Overview

The AgentCalls MCP server lets AI agents (Claude, Cursor, Claude Code, your own agents) operate your account through the Model Context Protocol: list and build voice agents, browse voices, search and buy numbers, start calls, read transcripts and summaries, manage contacts and check usage. Same organization, same API keys and scopes as the REST API.

Endpoint: https://agentcalls.net/mcp. Keys come from the dashboard under Settings → Developers → Connect an AI agent, which pre-selects the scopes below and shows your client's setup with the key filled in.

ClientAuthStatus
Claude.ai and Claude Desktop (custom connector)API key headerSupported
Claude CodeAPI key headerSupported
CursorAPI key headerSupported
ChatGPT (developer mode connectors)OAuth onlyNot yet (needs OAuth sign-in)
Any MCP client or SDK (Streamable HTTP)API key headerSupported

Connect a client

Claude.ai and Claude Desktop (custom connector)

  1. Open Customize → Connectors → Add custom connector (Team/Enterprise: Organization settings → Connectors → Add → Custom → Web).
  2. Name: AgentCalls. URL: https://agentcalls.net/mcp.
  3. Under Authentication choose "No sign in" (the server authenticates with your key, not OAuth).
  4. Under Request headers add Authorization with the value Bearer ak_live_… (your API key).
  5. Save, then ask Claude: "List my AgentCalls agents".

Claude Code

  1. Run once per project (or add --scope user):
claude mcp add --transport http agentcalls https://agentcalls.net/mcp \
  --header "Authorization: Bearer $AGENTCALLS_API_KEY"

Cursor

  1. Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global). Keep the key in an environment variable:
{
  "mcpServers": {
    "agentcalls": {
      "url": "https://agentcalls.net/mcp",
      "headers": {
        "Authorization": "Bearer ${env:AGENTCALLS_API_KEY}"
      }
    }
  }
}

ChatGPT (developer mode connectors)

OAuth required

  1. ChatGPT connectors accept only OAuth or unauthenticated servers; they cannot send an API key header.
  2. AgentCalls authenticates with API keys today, so ChatGPT cannot connect directly yet. OAuth 2.1 sign-in for the MCP endpoint is on the roadmap; until then use Claude, Cursor, Claude Code or any MCP client that supports headers.

Any MCP client or SDK (Streamable HTTP)

  1. Endpoint: POST https://agentcalls.net/mcp. Send Authorization: Bearer ak_live_… on every request, Accept: application/json, text/event-stream, and the MCP-Protocol-Version header your client speaks (2026-07-28 down to 2025-03-26 are served). No sessions: every request is independent.
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(new URL("https://agentcalls.net/mcp"), {
  authProvider: { token: async () => process.env.AGENTCALLS_API_KEY! },
}));
const { tools } = await client.listTools();
const usage = await client.callTool({ name: "get_usage", arguments: {} });

Authentication and scopes

Create an API key under Settings → Developers (or use "Connect an AI agent" there, which pre-selects the MCP scopes). Send it as Authorization: Bearer ak_live_… on every request.

Each tool needs one scope; a key without it gets a tool error forbidden naming the scope. Keys see only their organization's data. Revoking a key cuts the connection immediately.

The endpoint is a stateless Streamable HTTP server: no sessions, no server-initiated messages, every request independent, which is what hosted clients expect.

OAuth 2.1 (the sign-in flow ChatGPT requires) is not offered yet; clients must be able to send a fixed header.

ScopeTools
agents:readlist_agents, get_agent
agents:writecreate_agent, update_agent, publish_agent
voices:readlist_voices
numbers:readlist_numbers, search_numbers
numbers:writebuy_number, assign_number
calls:readlist_calls, get_call, get_transcript, get_recording_url
calls:writestart_call
contacts:readlist_contacts, get_contact
contacts:writeupsert_contact, set_do_not_call
webhooks:writecreate_webhook
usage:readget_usage

Tools

Tool names, descriptions and JSON schemas are written for models: each description says what the tool returns, which scope it needs and whether it spends money. Results are JSON (also returned as structuredContent).

ToolScopeKindSummary
list_agentsagents:readreadList agents
get_agentagents:readreadGet an agent
create_agentagents:writewriteCreate an agent
update_agentagents:writewriteUpdate an agent
publish_agentagents:writewritePublish an agent
list_voicesvoices:readreadList voices
list_numbersnumbers:readreadList phone numbers
search_numbersnumbers:readreadSearch available numbers
buy_numbernumbers:writewritecosts moneyBuy a phone number
assign_numbernumbers:writewriteAssign a number to an agent
start_callcalls:writewritecosts moneyStart an outbound call
get_callcalls:readreadGet a call
get_transcriptcalls:readreadGet a transcript
get_recording_urlcalls:readreadGet a recording link
list_callscalls:readreadList calls
list_contactscontacts:readreadList contacts
get_contactcontacts:readreadGet a contact
upsert_contactcontacts:writewriteCreate or update a contact
set_do_not_callcontacts:writewriteSet do-not-call
create_webhookwebhooks:writewriteCreate a webhook
get_usageusage:readreadGet plan and usage
list_agentsscope agents:readread-only

List agents

List the organization's voice agents with id, name, language, status, published version and voice tier. Use status: "live" to find agents that can take calls.

Returns. agents: array of { id, name, language, status, publishedVersion, voiceTier, firstMessage, createdAt }.

Example arguments

{
  "status": "live"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "status": {
      "default": "all",
      "description": "Filter by lifecycle state. `live` = has a published version.",
      "type": "string",
      "enum": [
        "all",
        "live",
        "draft",
        "archived"
      ]
    }
  }
}
get_agentscope agents:readread-only

Get an agent

Full configuration of one agent (draft): instructions, opening line, voice, consent, transfer number, hours, analysis schema, tools and publish state.

Returns. The agent object with every editable field, the voice as a list_voices id, and publishedVersion (0 = never published).

Example arguments

{
  "agentId": "6ad0bf49171339b53c8e"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "agentId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Agent id."
    }
  },
  "required": [
    "agentId"
  ]
}
create_agentscope agents:writewrites

Create an agent

Create a draft voice agent from a template, overriding any field you pass (instructions, opening line, voice, hours…). The configuration is validated with the voice platform before it is saved. The draft takes no calls until publish_agent.

Returns. The created agent (same shape as get_agent). Validation failures come back as an error listing the issues.

Example arguments

{
  "template": "receptionist",
  "name": "Front desk",
  "language": "en",
  "instructions": "You are Mia, the receptionist of Northwind Clinic…"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "template": {
      "default": "receptionist",
      "description": "Starting point for everything you do not set: instructions, opening line, consent text, summary prompt, analysis schema, built-in contact tools.",
      "type": "string",
      "enum": [
        "receptionist",
        "appointment_booking",
        "lead_qualification",
        "support"
      ]
    },
    "name": {
      "description": "Display name of the agent, e.g. \"Front desk\".",
      "type": "string",
      "minLength": 1,
      "maxLength": 80
    },
    "language": {
      "description": "Language the agent speaks and understands: en or ar. Changes the default voice and consent text.",
      "type": "string",
      "enum": [
        "en",
        "ar"
      ]
    },
    "voiceId": {
      "description": "Voice id from list_voices (not a provider voice id). Defaults to the language's default voice.",
      "type": "string",
      "maxLength": 64
    },
    "instructions": {
      "description": "System instructions in plain language: who the agent is, what it may do and say, what it must never do. Markdown allowed.",
      "type": "string",
      "minLength": 1,
      "maxLength": 20000
    },
    "firstMessage": {
      "description": "The agent's opening line when a call connects.",
      "type": "string",
      "minLength": 1,
      "maxLength": 600
    },
    "consentAnnouncement": {
      "description": "Play a recording announcement at the start of every call (default true; keep it on unless local law allows otherwise).",
      "type": "boolean"
    },
    "consentText": {
      "description": "The announcement text, in the agent's language.",
      "type": "string",
      "maxLength": 300
    },
    "transferNumber": {
      "description": "Human hand-off number for the transfer_call tool, or null to disable transfers.",
      "anyOf": [
        {
          "type": "string",
          "pattern": "^\\+[1-9]\\d{6,14}$",
          "description": "Phone number in E.164 format, e.g. +14155550123."
        },
        {
          "type": "null"
        }
      ]
    },
    "maxDurationSec": {
      "description": "Hard cap on call length in seconds (default 900).",
      "type": "integer",
      "minimum": 30,
      "maximum": 3600
    },
    "silenceTimeoutSec": {
      "description": "Seconds of silence before the agent ends the call (default 20).",
      "type": "integer",
      "minimum": 5,
      "maximum": 120
    },
    "endCallPhrases": {
      "description": "Phrases that end the call when the caller says them.",
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
      }
    },
    "voicemailDetection": {
      "description": "Hang up (outbound) when voicemail answers instead of talking to the machine (default true).",
      "type": "boolean"
    },
    "memoryEnabled": {
      "description": "Give the agent the contact's memory summary and let it update contact attributes (default true).",
      "type": "boolean"
    },
    "summaryPrompt": {
      "description": "Extra guidance for the post-call summary, or null for the default.",
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ]
    },
    "analysisSchema": {
      "description": "JSON Schema (object) of the structured data to extract after each call, e.g. { type: \"object\", properties: { appointment_confirmed: { type: \"boolean\" } } }. Null disables extraction.",
      "anyOf": [
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        },
        {
          "type": "null"
        }
      ]
    },
    "businessHours": {
      "description": "Opening hours the agent reads to callers; null means always open.",
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "timezone": {
              "type": "string",
              "maxLength": 64,
              "description": "IANA time zone, e.g. Asia/Dubai."
            },
            "days": {
              "maxItems": 7,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "day": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6,
                    "description": "0 = Sunday … 6 = Saturday."
                  },
                  "open": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "HH:MM"
                  },
                  "close": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "HH:MM"
                  }
                },
                "required": [
                  "day",
                  "open",
                  "close"
                ]
              }
            },
            "outsideHoursMessage": {
              "description": "What the agent says outside these hours.",
              "type": "string",
              "maxLength": 500
            }
          },
          "required": [
            "timezone",
            "days"
          ]
        },
        {
          "type": "null"
        }
      ]
    }
  }
}
update_agentscope agents:writewritesidempotent

Update an agent

Change fields of an agent's draft. Only the fields you pass change. Published calls keep using the last published version until you call publish_agent again.

Returns. The updated draft.

Example arguments

{
  "agentId": "6ad0bf49171339b53c8e",
  "firstMessage": "Thanks for calling Northwind Clinic, this is Mia."
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "agentId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Agent id."
    },
    "name": {
      "description": "Display name of the agent, e.g. \"Front desk\".",
      "type": "string",
      "minLength": 1,
      "maxLength": 80
    },
    "language": {
      "description": "Language the agent speaks and understands: en or ar. Changes the default voice and consent text.",
      "type": "string",
      "enum": [
        "en",
        "ar"
      ]
    },
    "voiceId": {
      "description": "Voice id from list_voices (not a provider voice id). Defaults to the language's default voice.",
      "type": "string",
      "maxLength": 64
    },
    "instructions": {
      "description": "System instructions in plain language: who the agent is, what it may do and say, what it must never do. Markdown allowed.",
      "type": "string",
      "minLength": 1,
      "maxLength": 20000
    },
    "firstMessage": {
      "description": "The agent's opening line when a call connects.",
      "type": "string",
      "minLength": 1,
      "maxLength": 600
    },
    "consentAnnouncement": {
      "description": "Play a recording announcement at the start of every call (default true; keep it on unless local law allows otherwise).",
      "type": "boolean"
    },
    "consentText": {
      "description": "The announcement text, in the agent's language.",
      "type": "string",
      "maxLength": 300
    },
    "transferNumber": {
      "description": "Human hand-off number for the transfer_call tool, or null to disable transfers.",
      "anyOf": [
        {
          "type": "string",
          "pattern": "^\\+[1-9]\\d{6,14}$",
          "description": "Phone number in E.164 format, e.g. +14155550123."
        },
        {
          "type": "null"
        }
      ]
    },
    "maxDurationSec": {
      "description": "Hard cap on call length in seconds (default 900).",
      "type": "integer",
      "minimum": 30,
      "maximum": 3600
    },
    "silenceTimeoutSec": {
      "description": "Seconds of silence before the agent ends the call (default 20).",
      "type": "integer",
      "minimum": 5,
      "maximum": 120
    },
    "endCallPhrases": {
      "description": "Phrases that end the call when the caller says them.",
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
      }
    },
    "voicemailDetection": {
      "description": "Hang up (outbound) when voicemail answers instead of talking to the machine (default true).",
      "type": "boolean"
    },
    "memoryEnabled": {
      "description": "Give the agent the contact's memory summary and let it update contact attributes (default true).",
      "type": "boolean"
    },
    "summaryPrompt": {
      "description": "Extra guidance for the post-call summary, or null for the default.",
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ]
    },
    "analysisSchema": {
      "description": "JSON Schema (object) of the structured data to extract after each call, e.g. { type: \"object\", properties: { appointment_confirmed: { type: \"boolean\" } } }. Null disables extraction.",
      "anyOf": [
        {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {}
        },
        {
          "type": "null"
        }
      ]
    },
    "businessHours": {
      "description": "Opening hours the agent reads to callers; null means always open.",
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "timezone": {
              "type": "string",
              "maxLength": 64,
              "description": "IANA time zone, e.g. Asia/Dubai."
            },
            "days": {
              "maxItems": 7,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "day": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6,
                    "description": "0 = Sunday … 6 = Saturday."
                  },
                  "open": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "HH:MM"
                  },
                  "close": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "HH:MM"
                  }
                },
                "required": [
                  "day",
                  "open",
                  "close"
                ]
              }
            },
            "outsideHoursMessage": {
              "description": "What the agent says outside these hours.",
              "type": "string",
              "maxLength": 500
            }
          },
          "required": [
            "timezone",
            "days"
          ]
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "agentId"
  ]
}
publish_agentscope agents:writewrites

Publish an agent

Validate the draft (locally and with the voice platform) and publish it as a new immutable version. From this moment inbound calls on assigned numbers and start_call use the new version.

Returns. { agentId, version, status: "live" }, or an error listing blocking issues.

Example arguments

{
  "agentId": "6ad0bf49171339b53c8e"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "agentId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Agent id."
    }
  },
  "required": [
    "agentId"
  ]
}
list_voicesscope voices:readread-only

List voices

Browse the curated English and Arabic voice catalog (accents, gender, standard/premium tier, preview audio). Pass the returned id as voiceId to create_agent / update_agent.

Returns. voices: array of { id, name, language, gender, accent, tier, description, previewUrl }.

Example arguments

{
  "language": "ar",
  "gender": "female"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "language": {
      "description": "Only voices for this language.",
      "type": "string",
      "enum": [
        "en",
        "ar"
      ]
    },
    "gender": {
      "type": "string",
      "enum": [
        "male",
        "female"
      ]
    },
    "tier": {
      "description": "Premium voices cost more per minute (see get_usage for the plan's rates).",
      "type": "string",
      "enum": [
        "standard",
        "premium"
      ]
    },
    "accent": {
      "description": "Substring match on the accent label, e.g. \"gulf\", \"egyptian\", \"british\".",
      "type": "string",
      "maxLength": 40
    }
  }
}
list_numbersscope numbers:readread-only

List phone numbers

The organization's phone numbers with status, assigned agent and whether they can dial out. Use an active number with inboundOnly: false as phoneNumberId for start_call.

Returns. numbers: array of { id, phone, country, type, status, agentId, inboundOnly, monthlyPriceCents, createdAt }.

Example arguments

{}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {}
}
search_numbersscope numbers:readread-onlyexternal effect

Search available numbers

Search the carrier inventory for numbers you could buy, with the monthly price for this organization and whether regulatory documents are needed before activation. Nothing is purchased.

Returns. numbers: candidates { phone, locality, region }, plus monthlyPriceCents, includedInPlan, inboundOnly, regulatory (none | documents) and eligibility.

Example arguments

{
  "country": "US",
  "type": "local",
  "areaCode": "415"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "country": {
      "type": "string",
      "minLength": 2,
      "maxLength": 2,
      "description": "ISO 3166-1 alpha-2 country code. Offered today: US, GB, AE, SA."
    },
    "type": {
      "default": "local",
      "description": "local, tollfree or mobile. Not every type exists in every country; the result says which.",
      "type": "string",
      "enum": [
        "local",
        "tollfree",
        "mobile"
      ]
    },
    "contains": {
      "description": "Digits the number should contain, e.g. \"555\" or a vanity pattern.",
      "type": "string",
      "maxLength": 12
    },
    "areaCode": {
      "description": "Area code (US and CA only).",
      "type": "string",
      "maxLength": 5
    },
    "limit": {
      "default": 10,
      "type": "integer",
      "minimum": 1,
      "maximum": 30
    }
  },
  "required": [
    "country"
  ]
}
buy_numberscope numbers:writewritesexternal effectcosts money · confirm: true

Buy a phone number

Buy a number from search_numbers for this organization (monthly charge on the subscription unless the plan includes it). Requires confirm: true. Countries that need regulatory documents are not bought here: the tool returns the document requirements and a dashboard link instead.

Returns. { purchased: true, number } when the number is active, or { purchased: false, reason, requirements?, dashboardUrl? }.

Example arguments

{
  "phone": "+14155550123",
  "country": "US",
  "type": "local",
  "confirm": true
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "phone": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "A number returned by search_numbers, in E.164."
    },
    "country": {
      "type": "string",
      "minLength": 2,
      "maxLength": 2,
      "description": "Country code used in the search."
    },
    "type": {
      "default": "local",
      "type": "string",
      "enum": [
        "local",
        "tollfree",
        "mobile"
      ]
    },
    "confirm": {
      "default": false,
      "description": "Must be true. Buying a number starts a monthly charge; ask the user before setting this.",
      "type": "boolean"
    }
  },
  "required": [
    "phone",
    "country"
  ]
}
assign_numberscope numbers:writewritesidempotent

Assign a number to an agent

Set (or clear with null) the agent that answers inbound calls on a number. The agent must be published to actually answer.

Returns. The updated number.

Example arguments

{
  "phoneNumberId": "68ee11aa00c1d2e3f405",
  "agentId": "6ad0bf49171339b53c8e"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "phoneNumberId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Phone number id."
    },
    "agentId": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Agent id."
        },
        {
          "type": "null"
        }
      ],
      "description": "Agent that answers inbound calls on this number, or null to unassign."
    }
  },
  "required": [
    "phoneNumberId",
    "agentId"
  ]
}
start_callscope calls:writewritesexternal effectcosts money · confirm: true

Start an outbound call

Dial a phone number with a published agent from one of the organization's numbers. Returns immediately with the call id while the phone rings; the conversation, summary and outcome arrive asynchronously (poll get_call, or receive the call.summary.ready webhook). Requires confirm: true.

Returns. { callId, status: "queued", note }. Errors: agent_not_published, number_not_active, number_inbound_only, payment_required, concurrency_limit, contact_do_not_call, country_not_allowed, call_start_failed.

Example arguments

{
  "agentId": "6ad0bf49171339b53c8e",
  "to": "+14155550123",
  "contactName": "Sara Ahmed",
  "idempotencyKey": "order-8841-confirm",
  "confirm": true
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "agentId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "A published agent (publishedVersion > 0)."
    },
    "to": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "Number to dial."
    },
    "phoneNumberId": {
      "description": "Your caller-id number. Optional when the organization has exactly one active outbound number.",
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    },
    "contactName": {
      "description": "Name passed to the agent when the number is not a known contact.",
      "type": "string",
      "maxLength": 120
    },
    "idempotencyKey": {
      "description": "Unique per intended call (e.g. an order id). Repeating a call with the same key returns the first call instead of dialing again.",
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "confirm": {
      "default": false,
      "description": "Must be true. A call costs money and rings a real phone; ask the user before setting this.",
      "type": "boolean"
    }
  },
  "required": [
    "agentId",
    "to"
  ]
}
get_callscope calls:readread-only

Get a call

Status and results of a call: direction, numbers, timing, cost, summary, outcome, sentiment, structured data and the actions the agent took (plain-language labels). The transcript is a separate tool (get_transcript).

Returns. The call object; summary/outcome are null until post-call analysis finishes (usually within a minute of hang-up). endedReason is a plain-language label.

Example arguments

{
  "callId": "68ef3b2c001a9d4e7f10"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "callId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Call id."
    }
  },
  "required": [
    "callId"
  ]
}
get_transcriptscope calls:readread-only

Get a transcript

The conversation of a call, as text or as structured turns. Empty while the call is in progress.

Returns. { callId, status, turns } (json) or { callId, status, text }.

Example arguments

{
  "callId": "68ef3b2c001a9d4e7f10",
  "format": "text"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "callId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Call id."
    },
    "format": {
      "default": "text",
      "description": "`text` = one line per turn (\"agent: …\"), `json` = array of { role, text, at }.",
      "type": "string",
      "enum": [
        "text",
        "json"
      ]
    }
  },
  "required": [
    "callId"
  ]
}
get_recording_urlscope calls:readread-only

Get a recording link

A signed, short-lived (10 minutes) URL to the call's audio recording. Fetch it again when it expires. Null when the call has no recording (in progress, failed, or deleted by retention).

Returns. { callId, url, expiresAt } or { callId, url: null, reason }.

Example arguments

{
  "callId": "68ef3b2c001a9d4e7f10"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "callId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Call id."
    }
  },
  "required": [
    "callId"
  ]
}
list_callsscope calls:readread-only

List calls

Calls newest first, with optional filters by agent, status, time window and contact. Paginate with cursor.

Returns. { calls: Call[], nextCursor }; each call is the get_call shape without summary text fields trimmed.

Example arguments

{
  "status": "ended",
  "since": "2026-10-01T00:00:00Z",
  "limit": 20
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "agentId": {
      "description": "Only calls handled by this agent.",
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    },
    "status": {
      "type": "string",
      "enum": [
        "queued",
        "ringing",
        "in_progress",
        "ended",
        "failed",
        "no_answer",
        "busy",
        "voicemail"
      ]
    },
    "since": {
      "description": "Started at or after this instant.",
      "type": "string",
      "maxLength": 40
    },
    "until": {
      "description": "Started at or before this instant.",
      "type": "string",
      "maxLength": 40
    },
    "contact": {
      "description": "A contact id or an E.164 phone number.",
      "type": "string",
      "maxLength": 64
    },
    "limit": {
      "default": 25,
      "description": "Page size, 1–100.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "cursor": {
      "description": "Opaque `nextCursor` from the previous page. Omit for the first page.",
      "type": "string",
      "maxLength": 200
    }
  }
}
list_contactsscope contacts:readread-only

List contacts

Contacts newest first, filtered by phone, tag or do-not-call flag. Paginate with cursor.

Returns. { contacts: Contact[], nextCursor }.

Example arguments

{
  "tag": "vip",
  "limit": 20
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "phone": {
      "description": "Exact match (at most one result).",
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$"
    },
    "tag": {
      "description": "Contacts carrying this tag.",
      "type": "string",
      "maxLength": 64
    },
    "doNotCall": {
      "type": "boolean"
    },
    "limit": {
      "default": 25,
      "description": "Page size, 1–100.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "cursor": {
      "description": "Opaque `nextCursor` from the previous page. Omit for the first page.",
      "type": "string",
      "maxLength": 200
    }
  }
}
get_contactscope contacts:readread-only

Get a contact

One contact by id or phone, including the memory summary the agent is given at the start of each call, attributes, tags, notes and call count.

Returns. The contact, or an error not_found.

Example arguments

{
  "phone": "+14155550123"
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "contactId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Contact id."
    },
    "phone": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "Phone number in E.164 format, e.g. +14155550123."
    }
  }
}
upsert_contactscope contacts:writewritesidempotent

Create or update a contact

Create the contact for a phone number or update it when it exists. Attributes merge; tags replace.

Returns. { created: boolean, contact }.

Example arguments

{
  "phone": "+14155550123",
  "name": "Sara Ahmed",
  "tags": [
    "vip"
  ],
  "attributes": {
    "plan": "pro"
  }
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "phone": {
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$",
      "description": "Identifies the contact: created when unknown, updated otherwise."
    },
    "name": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120
        },
        {
          "type": "null"
        }
      ]
    },
    "email": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 320,
          "format": "email",
          "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "attributes": {
      "description": "Key/value strings merged into the stored attributes (set a key to \"\" to clear it). The agent can read and update them during calls.",
      "type": "object",
      "propertyNames": {
        "type": "string",
        "maxLength": 64
      },
      "additionalProperties": {
        "type": "string",
        "maxLength": 500
      }
    },
    "tags": {
      "description": "Replaces the tag list.",
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      }
    },
    "notes": {
      "description": "Internal notes (not spoken by the agent).",
      "type": "string",
      "maxLength": 4000
    },
    "doNotCall": {
      "type": "boolean"
    }
  },
  "required": [
    "phone"
  ]
}
set_do_not_callscope contacts:writewritesidempotent

Set do-not-call

Block (or unblock) outbound calls to a contact. start_call refuses blocked contacts.

Returns. The contact with the new flag.

Example arguments

{
  "phone": "+14155550123",
  "doNotCall": true
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "contactId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Contact id."
    },
    "phone": {
      "description": "Creates the contact when it does not exist yet, so the flag sticks.",
      "type": "string",
      "pattern": "^\\+[1-9]\\d{6,14}$"
    },
    "doNotCall": {
      "default": true,
      "description": "true blocks outbound calls to this contact; false lifts the block.",
      "type": "boolean"
    }
  }
}
create_webhookscope webhooks:writewrites

Create a webhook

Register an HTTPS endpoint that receives signed events (call.ended, call.summary.ready, wallet.low). The signing secret is returned once; store it to verify the X-AgentCalls-Signature header.

Returns. { id, url, events, secret } — the secret is never shown again.

Example arguments

{
  "url": "https://example.com/agentcalls",
  "events": [
    "call.summary.ready"
  ]
}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "maxLength": 1024,
      "format": "uri",
      "description": "HTTPS endpoint that receives signed JSON events."
    },
    "events": {
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "call.ended",
          "call.summary.ready",
          "wallet.low"
        ]
      },
      "description": "Events to deliver."
    }
  },
  "required": [
    "url",
    "events"
  ]
}
get_usagescope usage:readread-only

Get plan and usage

Plan, included minutes left, prepaid wallet balance, number allowance, concurrency limit and per-minute overage rates. Check it before placing many calls or buying numbers.

Returns. { organization, plan, period, minutes, wallet, numbers, concurrency, rates }.

Example arguments

{}
Input schema (JSON Schema 2020-12)
{
  "type": "object",
  "properties": {}
}

Resources

Read-only documents a client can attach to its context. Templates are the same four starting points the dashboard offers; the voice catalog is what list_voices returns.

URINameDescription
agentcalls://templatesAgent templatesThe four agent templates (receptionist, appointment booking, lead qualification, support) in English and Arabic: instructions, opening line, consent text, analysis schema and built-in tools. Read one with agentcalls://templates/{template}/{language}.
agentcalls://templates/{template}/{language}One agent templateFull text of a template, e.g. agentcalls://templates/receptionist/ar.
agentcalls://voicesVoice catalogEvery voice with language, gender, accent, tier and preview URL (same data as list_voices).
agentcalls://voices/{language}Voices for a languageVoices for `en` or `ar`.

Prompts

Reusable instructions a client can insert as a user message. They tell the model which tools to call in which order and where to stop and ask the user.

design_receptionist_agent · Design a receptionist agent

Walks through choosing a voice, writing instructions, creating, publishing and assigning a number for a phone receptionist.

Arguments

{
  "type": "object",
  "properties": {
    "business": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "Business name and what it does, e.g. \"Northwind Dental Clinic in Dubai\"."
    },
    "language": {
      "default": "en",
      "description": "Language of the agent.",
      "type": "string",
      "enum": [
        "en",
        "ar"
      ]
    },
    "details": {
      "description": "Opening hours, services, prices, booking rules, escalation contact — anything the receptionist must know.",
      "type": "string",
      "maxLength": 2000
    }
  },
  "required": [
    "business"
  ]
}

review_recent_calls · Review recent calls

Pulls recent calls and summarizes outcomes, sentiment, failures and follow-ups.

Arguments

{
  "type": "object",
  "properties": {
    "since": {
      "description": "ISO date-time to start from (default: last 7 days).",
      "type": "string",
      "maxLength": 40
    },
    "agentId": {
      "description": "Restrict to one agent.",
      "type": "string",
      "maxLength": 64
    }
  }
}

place_follow_up_call · Place a follow-up call

Checks the contact and do-not-call status, confirms with the user, then starts the call and reports the result.

Arguments

{
  "type": "object",
  "properties": {
    "contact": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Contact id or E.164 phone number."
    },
    "goal": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500,
      "description": "What the call should achieve, e.g. \"confirm Thursday's 10:00 appointment\"."
    }
  },
  "required": [
    "contact",
    "goal"
  ]
}

Safety and money

  • buy_number and start_call spend money (a monthly number fee; per-minute call charges from included minutes or the prepaid wallet). Both require confirm: true; the tool descriptions tell the model to ask the user first.
  • start_call dials a real phone. Contacts marked do-not-call are refused; the organization's concurrency limit and billing state are enforced exactly as in the dashboard.
  • Calls are asynchronous: start_call returns while the phone rings. Poll get_call or subscribe to the call.summary.ready webhook; a tool call never waits for a phone call.
  • Mutating tools write an audit row (mcp.<tool>) with the key id, visible to administrators.

Limits

  • 60 requests per minute per key (shared with the REST API), enforced per server instance; a 429 carries Retry-After.
  • Lists return at most 100 items per page; nextCursor paginates.
  • Tool results are JSON in a text block and, for clients that read it, the same object as structuredContent.

Errors

Tool failures are returned as isError: true results with a JSON body { error: { code, message, details? } }. Codes match the REST API (invalid_request, forbidden, not_found, agent_not_published, number_not_active, payment_required, concurrency_limit, contact_do_not_call, country_not_allowed, call_start_failed, …) plus confirmation_required and validation_failed (agent configuration issues).

{
  "error": {
    "code": "forbidden",
    "message": "This API key does not have the numbers:write scope. Add it under Settings → Developers.",
    "details": {
      "required": "numbers:write",
      "granted": [
        "agents:read",
        "calls:read"
      ]
    }
  }
}

HTTP-level failures use the REST API envelope and status codes: 401 unauthorized (missing, unknown or revoked key), 403 (origin not allowed), 429 rate_limited with Retry-After.

Protocol details

Transport: Streamable HTTP, one POST per JSON-RPC message, JSON responses (SSE only when a tool streams notifications). Protocol versions served: 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26. Clients on the 2025 revisions may send initialize; it is answered statelessly, no session id is minted, and GET or DELETE on the endpoint answer 405.

Browsers may not call the endpoint cross-site (an Origin header other than agentcalls.net is refused with 403); every supported client connects from a server or a desktop process and sends none. The server is built on the official TypeScript SDK (@modelcontextprotocol/server).

Prefer REST? The same keys work on the REST API v1; its OpenAPI document lists this endpoint under x-mcp.