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.
| Client | Auth | Status |
|---|---|---|
| Claude.ai and Claude Desktop (custom connector) | API key header | Supported |
| Claude Code | API key header | Supported |
| Cursor | API key header | Supported |
| ChatGPT (developer mode connectors) | OAuth only | Not yet (needs OAuth sign-in) |
| Any MCP client or SDK (Streamable HTTP) | API key header | Supported |
Connect a client
Claude.ai and Claude Desktop (custom connector)
- Open Customize → Connectors → Add custom connector (Team/Enterprise: Organization settings → Connectors → Add → Custom → Web).
- Name: AgentCalls. URL: https://agentcalls.net/mcp.
- Under Authentication choose "No sign in" (the server authenticates with your key, not OAuth).
- Under Request headers add
Authorizationwith the valueBearer ak_live_…(your API key). - Save, then ask Claude: "List my AgentCalls agents".
Claude Code
- 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
- 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
- ChatGPT connectors accept only OAuth or unauthenticated servers; they cannot send an API key header.
- 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)
- 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.
| Scope | Tools |
|---|---|
agents:read | list_agents, get_agent |
agents:write | create_agent, update_agent, publish_agent |
voices:read | list_voices |
numbers:read | list_numbers, search_numbers |
numbers:write | buy_number, assign_number |
calls:read | list_calls, get_call, get_transcript, get_recording_url |
calls:write | start_call |
contacts:read | list_contacts, get_contact |
contacts:write | upsert_contact, set_do_not_call |
webhooks:write | create_webhook |
usage:read | get_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).
| Tool | Scope | Kind | Summary |
|---|---|---|---|
| list_agents | agents:read | read | List agents |
| get_agent | agents:read | read | Get an agent |
| create_agent | agents:write | write | Create an agent |
| update_agent | agents:write | write | Update an agent |
| publish_agent | agents:write | write | Publish an agent |
| list_voices | voices:read | read | List voices |
| list_numbers | numbers:read | read | List phone numbers |
| search_numbers | numbers:read | read | Search available numbers |
| buy_number | numbers:write | writecosts money | Buy a phone number |
| assign_number | numbers:write | write | Assign a number to an agent |
| start_call | calls:write | writecosts money | Start an outbound call |
| get_call | calls:read | read | Get a call |
| get_transcript | calls:read | read | Get a transcript |
| get_recording_url | calls:read | read | Get a recording link |
| list_calls | calls:read | read | List calls |
| list_contacts | contacts:read | read | List contacts |
| get_contact | contacts:read | read | Get a contact |
| upsert_contact | contacts:write | write | Create or update a contact |
| set_do_not_call | contacts:write | write | Set do-not-call |
| create_webhook | webhooks:write | write | Create a webhook |
| get_usage | usage:read | read | Get plan and usage |
list_agentsscope agents:readread-onlyList 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-onlyGet 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:writewritesCreate 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:writewritesidempotentUpdate 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:writewritesPublish 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-onlyList 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-onlyList 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 effectSearch 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: trueBuy 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:writewritesidempotentAssign 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: trueStart 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-onlyGet 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-onlyGet 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-onlyGet 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-onlyList 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-onlyList 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-onlyGet 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:writewritesidempotentCreate 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:writewritesidempotentSet 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:writewritesCreate 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-onlyGet 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.
| URI | Name | Description |
|---|---|---|
agentcalls://templates | Agent templates | The 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 template | Full text of a template, e.g. agentcalls://templates/receptionist/ar. |
agentcalls://voices | Voice catalog | Every voice with language, gender, accent, tier and preview URL (same data as list_voices). |
agentcalls://voices/{language} | Voices for a language | Voices 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_numberandstart_callspend money (a monthly number fee; per-minute call charges from included minutes or the prepaid wallet). Both requireconfirm: true; the tool descriptions tell the model to ask the user first.start_calldials 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_callor subscribe to thecall.summary.readywebhook; 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;
nextCursorpaginates. - 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.