Announcing CIMD support for MCP Client registration
Learn more

MCP vs API for Integrating Dialpad to AI Agents

Nityashree Yadunath
Product Marketing Manager

Dialpad hosts an official MCP server in its Early Access Program that signs each user in through the browser and acts with their existing access. The Dialpad API v2 covers what happens after a call: AI Recaps, SMS, screen pops, stats exports and call events. The credential choice on the API is stark, a per-user OAuth grant or a company key that reaches every user. This guide compares both for sales and support agents and shows how to run per-rep grants across many Dialpad companies.

TL;DR

  • Dialpad hosts an official MCP server at https://mcp.dialpad.com/mcp over Streamable HTTP, offered as an Early Access Program feature that you request through your Customer Success Manager. Companies outside the program get 403 Forbidden. Its docs say 36 tools while its tables list 42, across calls, call centers, coaching teams, contacts, departments and offices, dispositions, users, and transcript and message search. It has no SMS send, no AI Recap tool, no stats exports, and no event subscriptions.
  • The MCP server signs each user in through the browser with Dynamic Client Registration (DCR), acts with that user's existing API access, and expires sessions after 24 hours. The Dialpad API v2 at https://dialpad.com/api/v2 takes OAuth 2.0 tokens (PKCE recommended, refresh tokens only with offline_access) or company API keys that only a Company Admin can create and that "allow a consumer to perform actions across all users in the company."
  • Dialpad rate-limits the API at 20 requests per second per company, with tighter per-endpoint limits: 100 SMS per minute, 5 call initiations per minute per user, and 12 AI Recap reads per minute. MCP tool calls count against the same limits as the underlying API requests.
  • MCP is the faster path for a sales lead in Claude or Cursor asking what was promised on yesterday's calls. The API is the path for agents that react to hangup and recap_summary call events, send SMS follow-ups, pull AI Recaps, run stats exports, or serve many Dialpad companies.
  • Scalekit's dialpad connector exposes 112 prebuilt tools over the Dialpad API (68 read, 42 write, 2 destructive) with per-user OAuth through your own Dialpad OAuth app. Scalekit stores and refreshes each user's tokens, so the MCP vs API decision doesn't change your auth infrastructure.

The decision your Dialpad agent forces first

Your agent needs to work inside your customers' Dialpad accounts: pull the AI Recap and action items from a rep's discovery call and write them to the CRM, text a prospect a follow-up five minutes after the call ends, pop the right CRM record on an agent's screen when a call center call connects, or tell a support manager which queue missed the most calls last week. Dialpad now hosts an official Model Context Protocol (MCP) server that Claude Code, Claude Desktop, and Cursor can connect to. It also has the Dialpad API v2, event subscriptions for calls, SMS, contacts, and agent status, and a Stats API. They overlap on calls, contacts, and transcripts. They diverge on messaging, events, and how much of the company one credential can reach. Here's how to pick.

What Dialpad MCP and Dialpad API actually are

Dialpad MCP

The Dialpad MCP server is a remote endpoint hosted by Dialpad at https://mcp.dialpad.com/mcp, using the Streamable HTTP transport. Nothing runs locally. Dialpad labels it "an Early Access Program feature" and tells you to contact your Customer Success Manager for access; "your company must be enrolled in the Early Access Program for the server to return any data." Dialpad has published no GA date.

Dialpad documents the tools by name. The page states "36 tools are exposed by the Dialpad MCP Server today," but its tables list 42:

  • Calls (12): list-call, get-call-info, initiate-call, initiate-ivr-call, add-call-participant, transfer-call, hangup-call, unpark-call, assign-call, unassign-call-operator, unassign-all-call-operator, set-call-label
  • Search and transcripts (5): search-messages, search-call-transcripts, fetch-call-transcripts, search-meeting-transcripts, fetch-meeting-transcripts
  • Contacts (6): list-contacts, get-contact, create-contact, create-with-uid-contact, update-contact, delete-contact
  • Dispositions (5), call centers and call labels (4), departments and offices (5), coaching teams (3), and users (get-user, search-users)

Auth is a browser sign-in with your Dialpad account. Clients register themselves through DCR on first connection, so there is no API key, service account, or admin setup step. "Sessions last 24 hours. After that, your client prompts you to sign in again." The server can reach only data the signed-in account could already reach through Dialpad's public APIs, and "some endpoints require a 'company administrator' role." The docs list no OAuth scopes.

Dialpad's marketing page at dialpad.com/mcp describes "100+ pre-built actions across calls, messages, meetings, transcripts, and settings" and shows an API key in a local config. The developer documentation above is the more specific source, and it is the one this post uses.

Dialpad API

The Dialpad API v2 is REST at https://dialpad.com/api/v2/, with a separate sandbox at https://sandbox.dialpad.com/api/v2/. It covers calls and call control, AI Recaps and transcripts, SMS, contacts, users, offices, departments, call centers and operators, callbacks, call routers, meetings and rooms, dispositions, scorecards, the Stats API, webhooks, and event subscriptions for calls, SMS, contacts, agent status, and change logs.

Two ways to authenticate, both sent as Authorization: Bearer:

  • OAuth 2.0: for apps that users at other Dialpad companies connect. You register an app through Dialpad's form with your redirect URIs and any special scopes, and Dialpad issues a client ID and secret. Authorization starts at https://dialpad.com/oauth2/authorize, tokens come from https://dialpad.com/oauth2/token, and Dialpad recommends PKCE (RFC 7636) (OAuth docs).
  • API key: for internal apps. "Only Company Admins can create API keys on Dialpad," under Admin Settings > My Company > Authentication > API Keys, with an expiration and optional additional scopes chosen at creation (API key docs).

Dialpad also accepts the key as an ?apikey= query parameter and advises against it, because the key shows up in logs.

Comparing them where it matters for agents

What your agent can actually do

Capability
Dialpad MCP (mcp.dialpad.com)
Dialpad API (dialpad.com/api/v2)
List and read calls
Yes: Concluded calls
Yes: Listing calls needs an extra scope
Call transcripts
Yes
Limited: Per call by ID; no search
AI Recap summary and action items
No
Yes: Short, medium, long or bullet format; needs a recap scope, 12 per minute
Send SMS or MMS
No
Yes: Up to 10 recipients or a channel hashtag; 100 per minute
Search message history
Yes: Across SMS, MMS, and native messages
Limited: SMS content arrives through SMS event subscriptions with a content export scope
Call control: dial, transfer, hang up, add a participant
Yes
Yes: 5 initiations per minute per user
Queue operators and callbacks
Limited: Assign and unassign tools; no callbacks
Yes
Contacts
Yes
Yes: Create, update and upsert keyed on your CRM record ID
Meetings
Yes
Yes
Live-call actions: screen pop, recording, Voice Intelligence
No
Yes
Analytics exports
No
Yes: Stats API exports, created then fetched
React to calls, SMS, and agent status
No: No events or triggers
Yes: Webhooks plus call, SMS, contact, agent status, and change log subscriptions; set up outside the connector

Dialpad built its MCP server for a person working a phone system from a chat window: find the call, read what was said, move a caller, fix a contact. Its one clear advantage is search. search-call-transcripts and search-messages look across conversations, where the API's transcript endpoint takes one call_id at a time. Everything after a call, the recap, the follow-up text, the analytics, and the event that tells an agent a call ended, lives only on the API.

What the MCP path makes easier

"What did customers say about the new pricing on calls this week" on the raw API means listing calls with started_after in Unix milliseconds, paging with a cursor, then fetching a transcript per call_id and scanning each one in the model's context. Every fetch counts against the company's 20 requests per second, shared with every other integration your customer runs.

Dialpad MCP turns that into one search-call-transcripts call and a few fetch-call-transcripts calls on the matches. Setup in Claude Code or Cursor is a URL and a sign-in, because DCR means no app registration. Writing the schema is the hard part. Not the API call.

The tradeoff is that Dialpad owns the surface, and the surface is still moving: an Early Access server whose own docs disagree on the tool count. Dialpad also warns that assistants "may not page on their own," so a model that reads the first page of list-call can answer as if it saw everything. An agent tuned against today's 42 tools will see the set change without a release on your side.

The auth path each one puts you on

On the MCP path, auth is a browser sign-in with DCR, and the result is a user-level identity. The server "acts on your behalf" and reaches what that user can already reach through the public APIs. Tools that need a company administrator fail for everyone else. A session lasts 24 hours, so an MCP client running a scheduled job will hit a sign-in prompt the next day with no user there to answer it. Dialpad's MCP docs say nothing about scopes, listing active sessions, or revoking one.

On the direct API path, you choose between an OAuth grant and a company API key, and they are different in kind. An OAuth token belongs to the user who approved your app's consent screen. Apps get basic access by default, and every additional scope, including calls:list, recordings_export, message_content_export, screen_pop, and offline_access, must be approved by Dialpad through support. Without offline_access you get no refresh token. With it, each refresh returns a new access token and a new refresh token to persist. The authorization code is valid for 1 minute. Dialpad's OAuth guide does not state an access token lifetime. Revocation is a POST to https://dialpad.com/oauth2/deauthorize with the user's token, which revokes every token your app holds for that user.

A company API key is the opposite shape. It is created by a Company Admin, and Dialpad's own docs warn that a key reaches further than a password because keys "allow a consumer to perform actions across all users in the company." In practice, an agent holding one can act as any rep: place calls, send SMS, and read transcripts across the company. What the user can't do, the agent can't do. That only holds when the credential belongs to the user the agent is acting for, and a company key belongs to no one in particular.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP gives you a session per user and the direct API gives you an OAuth grant per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

Dialpad raises the stakes because the shortcut is so tempting. One admin API key per customer company makes every endpoint work on day one, including the ones a rep's own OAuth grant can't reach. It also means a rep who asks your agent to "summarize my calls" can ask about the VP's calls instead and get an answer, and dialpad_sms_send can send from any number in the company. The safer setup is one OAuth grant per Dialpad user, so Dialpad's own permissions do the filtering. That multiplies the credentials you hold, and rotating refresh tokens mean each refresh must be persisted before the old one is discarded.

Recommended reading: OAuth vs API keys for AI agents

What you own in production

On the MCP path, Dialpad hosts the server, owns the tool schemas, and enforces each user's access. You own the 24-hour re-authentication, the pagination the model skips, and the gaps. Nothing happens when a call ends, because tools only run inside a chat someone starts. Dialpad's troubleshooting note says to "confirm the URL matches your company's region," so check the endpoint with your account team before you assume one URL fits every customer. Tool calls draw on the same rate limits as the API, so a chatty assistant can throttle your customer's other integrations.

On the API path, you own schemas, pagination, retries, and the token lifecycle. Rate limits are 20 requests per second per company, plus per-endpoint ceilings: 100 contact creations and 100 SMS per minute, 5 call initiations per minute per user, 5 callback requests per minute to one number, and one call router update every 5 minutes. The AI Recap endpoint is limited to 12 per minute, so a pipeline that fetches a recap for every call in a 50-seat call center needs a queue in front of it. Dialpad "reserves the right to change the above limits."

Event subscriptions are how a headless agent learns something happened. You create a webhook with POST /api/v2/webhooks, a hook_url, and a secret, then subscribe it to call states such as hangup, call_transcription, and recap_summary for a user, department, office, or call center. With a secret set, each event arrives as a JWT signed with HS256; without one, it arrives as plain JSON that anyone could forge. Subscriptions are capped at 100 per type per company, and 10 per type for subscriptions that target the company itself. Recording URLs appear in call events only if the credential has recordings_export.

When to use MCP, when to use the API

Use Dialpad MCP when:

  • A sales manager in Claude asks what objections came up on this week's demo calls, and search-call-transcripts finds them across reps without anyone building an integration.
  • A support lead wants to move a waiting caller, assign an operator to a queued call, or fix a contact's number from Cursor, with Dialpad's own permissions deciding what they can touch.
  • A RevOps admin is cleaning up dispositions and call labels and wants each change made as themselves.
  • Your company is in Dialpad's Early Access Program and you are evaluating AI-assisted calling on your own account, with each person signing in under their own login.

Use the Dialpad API when:

  • The agent runs headless: a recap_summary call event fires, and the agent pulls the AI Recap with dialpad_call_ai_recap_get, labels the call, and writes the action items to your CRM.
  • The agent sends messages: an SMS follow-up with dialpad_sms_send after a missed call, within the 100-per-minute limit.
  • The agent acts during a live call: dialpad_user_screen_pop_trigger opens the caller's CRM record on the rep's screen, or dialpad_user_call_recording_toggle starts a recording.
  • You serve many Dialpad companies and need an OAuth grant per user, subscriptions per customer, Stats API exports, and backoff tuned to each company's shared limit.

The credential problem that exists on both paths

Both paths hand you a Dialpad credential per user. Neither hands you a vault. A multi-tenant revenue agent serving 60 customer companies with 25 reps each holds 1,500 credentials if it does isolation properly. On the MCP path, that is 1,500 sessions that expire every 24 hours and live in clients you may not control. On the API's OAuth path, it is 1,500 grants with rotating refresh tokens, each carrying whichever extra scopes Dialpad approved for your app. Lose a write race between two workers refreshing the same grant and one of them persists a dead refresh token; the rep has to sign in again. Every grant can place calls and read transcripts as its owner. Each must be stored encrypted, kept out of logs and LLM context, mapped to exactly one user at one company, and deauthorized when that user leaves.

The tempting shortcut is one Company Admin API key per customer. Sixty secrets instead of 1,500. Each one reaches every user, every call, and every phone number in its company, and every SMS your agent sends goes out with no record in Dialpad of which rep asked for it. Fewer secrets, larger blast radius per secret.

Scalekit's dialpad connector runs the OAuth flow per user, stores each user's tokens in the token vault, and refreshes them, so the MCP vs API decision doesn't change your auth infrastructure. Your agent passes an identifier; credentials never touch the agent runtime or the LLM context.

Recommended reading: how to handle token refresh for AI agents

How to connect a Dialpad agent with Scalekit

Scalekit's dialpad connector is an API-path connector built by Scalekit with 112 tools: 68 read, 42 write, and 2 destructive. It covers calls and call control, AI Recaps and transcripts, SMS and scheduled messages, contacts, users and devices, caller ID, offices, departments, call centers and operators, callbacks, call routers, channels, coaching teams, dispositions, meetings and rooms, scorecards, Stats API exports, and workforce management metrics. Some tools need extra scopes on the connection: dialpad_call_ai_recap_get needs ai_recap, dialpad_meeting_summaries_list needs conference:read, dialpad_user_screen_pop_trigger needs screen_pop, and dialpad_agent_group_get needs agent_groups:read. The core set for a call follow-up agent:

Tool
What it does
dialpad_users_search
Finds Dialpad users by name, email, or phone number
dialpad_calls_list
Lists calls, filtered by start time and by user, office, department, or call center
dialpad_call_get
Returns one call's direction, participants, duration, recordings, and routing
dialpad_call_ai_recap_get
Returns the AI Recap summary and action items in short, medium, long, or bullet form
dialpad_transcript_get
Returns the full Dialpad AI transcript with detected moments
dialpad_call_labels_add
Adds labels to a call, keeping existing ones and skipping duplicates
dialpad_contact_upsert
Creates or updates a shared contact keyed on your own uid, such as a CRM record ID
dialpad_sms_send
Sends an SMS or MMS from a user, a group, or a specific number

The connector authenticates with OAuth 2.0 through your own Dialpad OAuth app. Webhook and event subscription setup is not among the documented tools.

Prerequisites and the connection name

In the Scalekit dashboard, go to AgentKit > Connections, create a Dialpad connection, and copy its redirect URI. Register a Dialpad OAuth app through Dialpad's app request form with that redirect URI, and ask Dialpad to approve the extra scopes your agent needs: calls:list for call history, ai_recap for recaps, and offline_access so tokens can be refreshed. Enter the app's client ID and client secret on the Scalekit connection. The name you give the connection is the connection_name your code passes, and the string must match the dashboard exactly. This is the most common integration error. Build against sandbox.dialpad.com first; the sandbox has its own OAuth and API endpoints.

Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env. See the connector docs.

pip install scalekit-sdk-python python-dotenv langchain-openai pyjwt requests

Authorize each user once

get_or_create_connected_account returns the connected account for an identifier. If it is not ACTIVE, get_authorization_link returns a link the user opens once to approve your app on Dialpad's consent screen. Scalekit exchanges the code, stores the tokens, and handles refresh. The identifier is your app's ID for the rep, because a Dialpad OAuth token acts as exactly one Dialpad user. A first call to dialpad_blocked_numbers_list confirms the grant works before the agent touches calls.

import os from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions CONNECTION_NAME = "dialpad" # must match the connection name in AgentKit > Connections identifier = "acme_rep_2093" # your app's stable ID for this rep at this customer account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=identifier, ) print("Authorize Dialpad:", link.link) input("Press Enter after approving access in Dialpad...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"Dialpad is {account.connected_account.status}, not ACTIVE") check = actions.execute_tool( tool_name="dialpad_blocked_numbers_list", tool_input={}, connection_name=CONNECTION_NAME, identifier=identifier, ) print(check.execution_id, check.data)

Retrieve the Dialpad tools this user can call

This is not a catalog load. The agent asks for the tools this rep's connected account is authorized to call, then narrows them to the six a read-only call review assistant needs. Loading all 112 would put dialpad_sms_send, dialpad_user_call_initiate, and dialpad_call_hangup in front of a model whose job is to answer questions about past calls. None of the six below changes anything in Dialpad or reaches a customer's phone.

tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=[ "dialpad_users_search", "dialpad_calls_list", "dialpad_call_get", "dialpad_call_ai_recap_get", "dialpad_transcript_get", "dialpad_contacts_list", ], page_size=100, ) tool_map = {t.name: t for t in tools} print(sorted(tool_map))

Run the agent loop

The loop runs until the model stops requesting tools. Every call runs with this rep's Dialpad grant. The system prompt carries the details that trip models up on this API: user IDs must be looked up, started_after is Unix milliseconds, the recap endpoint is rate-limited, and lists are paginated.

import time from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) week_ago_ms = int((time.time() - 7 * 24 * 3600) * 1000) messages = [ SystemMessage( "You help a sales rep review their Dialpad calls. Find the rep's Dialpad user ID " "with dialpad_users_search using their email, rep@acme.example; never guess IDs. " "List calls with dialpad_calls_list using target_type 'user' and that target_id. " "started_after and started_before are UTC Unix timestamps in milliseconds. " "If a response includes a cursor, call again with it until there is none. " "Use dialpad_call_ai_recap_get with summary_format 'bullet' for action items; it is " "limited to 12 calls per minute, so only fetch recaps for calls you report on. " "Use dialpad_transcript_get only when the recap does not answer the question. " "You cannot call, text, or change anything in Dialpad; never claim you did." ), HumanMessage( f"For my outbound calls since {week_ago_ms} (Unix ms), list every action item I " "committed to, grouped by contact, and flag any call longer than 10 minutes that " "has no recap." ), ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: tool = tool_map.get(tc["name"]) if tool: result = tool.invoke(tc["args"]) else: result = f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

For pipelines, skip the model. execute_tool runs one tool with a fixed input and returns data plus an execution_id you can log. This handler runs when a Dialpad call event with the recap_summary state arrives. It verifies the HS256 JWT, maps the rep who took the call to your identifier, reads the call and its bullet-form AI Recap with that rep's own grant, and labels calls that produced action items so a manager can filter for them in Dialpad.

import jwt # PyJWT def identifier_for_rep(email: str) -> str: # Map the Dialpad user on the call to your app's ID for that rep return REP_IDENTIFIERS[email.lower()] REP_IDENTIFIERS = {"rep@acme.example": "acme_rep_2093"} def handle_dialpad_call_event(raw_body: bytes) -> None: secret = os.environ["DIALPAD_WEBHOOK_SECRET"] # set on the webhook, stored per customer try: event = jwt.decode(raw_body.decode(), secret, algorithms=["HS256"]) except jwt.InvalidTokenError as exc: raise PermissionError("Invalid Dialpad event signature") from exc if event.get("state") != "recap_summary": return call_id = int(event["call_id"]) target = event.get("target") or {} if target.get("type") != "user" or not target.get("email"): return # call center and department legs need their own mapping rep_id = identifier_for_rep(target["email"]) call = actions.execute_tool( tool_name="dialpad_call_get", tool_input={"call_id": call_id}, connection_name=CONNECTION_NAME, identifier=rep_id, ) print(call.execution_id, call_id) recap = actions.execute_tool( tool_name="dialpad_call_ai_recap_get", tool_input={"call_id": call_id, "summary_format": "bullet"}, connection_name=CONNECTION_NAME, identifier=rep_id, ) print(recap.execution_id, call_id) action_items = (recap.data or {}).get("action_items") or [] if not action_items: return labeled = actions.execute_tool( tool_name="dialpad_call_labels_add", tool_input={"call_id": call_id, "labels": ["has-action-items"]}, connection_name=CONNECTION_NAME, identifier=rep_id, ) print(labeled.execution_id, [item.get("content") for item in action_items])

dialpad_call_labels_add keeps existing labels and skips duplicates, so a redelivered event does not double-label the call. Make the CRM write that follows idempotent on call_id for the same reason. The recap read is the bottleneck: at 12 per minute, push events onto a queue and drain it at that rate rather than calling the endpoint inline from the webhook.

Creating the event subscription

The subscription itself is REST work you do once per customer. It is not a connector tool, and the MCP server has no equivalent. This example runs against the sandbox with a token for the customer's Dialpad account and subscribes one office to hangup and recap_summary.

import os import requests DIALPAD_API = "https://sandbox.dialpad.com/api/v2" # https://dialpad.com/api/v2 in production headers = { "Authorization": f"Bearer {os.environ['DIALPAD_ACCESS_TOKEN']}", "Accept": "application/json", } webhook = requests.post( f"{DIALPAD_API}/webhooks", json={ "hook_url": "https://agent.example.com/dialpad/events", "secret": os.environ["DIALPAD_WEBHOOK_SECRET"], # use a unique secret per customer }, headers=headers, timeout=30, ) webhook.raise_for_status() webhook_id = webhook.json()["id"] subscription = requests.post( f"{DIALPAD_API}/subscriptions/call", json={ "endpoint_id": webhook_id, "call_states": ["hangup", "recap_summary"], "target_type": "office", "target_id": int(os.environ["DIALPAD_OFFICE_ID"]), "enabled": True, }, headers=headers, timeout=30, ) if subscription.status_code == 429: raise RuntimeError("Dialpad rate limit hit; back off and retry") subscription.raise_for_status() print(webhook_id, subscription.json())

Two Dialpad specifics matter here. Set the secret: without it, events arrive as unsigned JSON. And recording URLs only appear in call events when the token has recordings_export, so a pipeline that needs recordings must request that scope before the first rep connects.

Multi-tool, multi-tenant Dialpad agents on a virtual MCP server

The dialpad connector has 112 tools. At roughly 200 tokens each, that is about 22,400 tokens of tool definitions before the agent does any work. A model choosing among dialpad_calls_list, dialpad_callbacks_list, dialpad_callcenter_operators_list, and dialpad_office_operators_list picks the wrong one more often than a model holding only the one it needs. Overreach is the bigger risk on a phone system: an assistant holding dialpad_user_call_initiate can ring a customer, and one holding dialpad_sms_send can text one. The fix is not better prompting. It is surface reduction. A Scalekit virtual MCP server declares exactly which tools an agent role can see, and session tokens keep each run bound to one user's connected accounts. Docs: set up a virtual MCP server.

Create the server once per agent role

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="dialpad-call-followup", description="Reviews a rep's Dialpad calls and AI Recaps and posts a Slack digest", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="dialpad", tools=[ "dialpad_users_search", "dialpad_calls_list", "dialpad_call_get", "dialpad_call_ai_recap_get", "dialpad_transcript_get", ], ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url)

Mint a session token per user, per run

One server definition serves every rep at every customer. Before each run, check that the user's accounts are active and mint a short-lived session token scoped to them. Any MCP client that speaks Streamable HTTP can connect; this example uses langchain-mcp-adapters (pip install langchain-mcp-adapters) and passes the token as a Bearer header.

import asyncio from datetime import timedelta from langchain_mcp_adapters.client import MultiServerMCPClient config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url user_id = "acme_rep_2093" accounts = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=user_id, include_auth_link=True, ) not_ready = [ a for a in accounts.connected_accounts if str(a.connected_account_status).upper() != "ACTIVE" ] for a in not_ready: print(f"{a.connection_name} is {a.connected_account_status}: {a.authentication_link}") if not_ready: raise RuntimeError("Connect the accounts above before starting this run") session_token = actions.mcp.create_session_token( mcp_config_id=config_id, identifier=user_id, expiry=timedelta(minutes=30), ).token async def load_tools(): client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": mcp_server_url, "headers": {"Authorization": f"Bearer {session_token}"}, } } ) return await client.get_tools() mcp_tools = asyncio.run(load_tools()) print([t.name for t in mcp_tools])

The agent sees six tools instead of 112 plus everything Slack exposes. The endpoint is static; the identity is per user. Dialpad's MCP docs describe no way to keep hangup-call or delete-contact away from an assistant whose job is reading. The virtual server makes that choice once per agent role, and it can include dialpad_call_ai_recap_get, which Dialpad's server does not offer at all.

Observability: every Dialpad action tied to a user

Every execute_tool call returns an execution_id, and every execution is attributed to a connected account, which maps to one rep at one customer. When a customer asks which agent run texted a prospect at 21:40, or who transferred a caller out of the support queue, the answer is a lookup, not an investigation. Dialpad records the user side of the same action: an SMS sent with a rep's grant defaults to that rep as sender, and a call placed with dialpad_user_call_initiate rings from that rep's line.

Attribution is where the API's two auth modes split. An OAuth grant acts as one user, so Dialpad's records and your execution log agree. A Company Admin API key acts across all users, so nothing in Dialpad says which of your users asked for a given call or message, and your log is the only record that does. Shared credentials are a single-user solution. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your agent is a rep's or manager's assistant in Claude Code, Claude Desktop, or Cursor, searching call transcripts and messages, moving callers, and fixing contacts, and your company is in Dialpad's Early Access Program, the hosted MCP server is a good fit: it acts as the signed-in user, and its transcript search is something the API does not offer. If your agent runs unattended, reacts to call events, reads AI Recaps, sends SMS, triggers screen pops, runs stats exports, or serves many Dialpad companies, build against the Dialpad API, where you choose the scopes, the tools, and the backoff against a limit your customer shares with everything else they run. Either way, you hold a Dialpad credential per user that can place calls and read conversations, and that's what needs production-grade infrastructure.

FAQs

Does Dialpad have an official MCP server?

Yes. Dialpad hosts an MCP server over Streamable HTTP as an Early Access Program feature that you request through your Customer Success Manager, and companies outside the program get a 403. Its docs list tools for calls, call centers, coaching teams, contacts, departments, dispositions, users and transcript and message search.

How does Dialpad MCP authentication work?

Each user signs in through the browser, and clients register themselves with Dynamic Client Registration, so there is no API key or admin setup step. The server acts with that user's existing API access, some tools need a company administrator, and sessions expire after 24 hours, which breaks scheduled jobs that have no user present.

What can the Dialpad API do that MCP cannot?

The API pulls AI Recap summaries and action items, sends SMS and MMS, triggers screen pops, toggles recording, runs Stats API exports and delivers call, SMS, contact and agent status events. The MCP server has none of those. Its one clear advantage is search across call transcripts and messages.

What is the difference between Dialpad OAuth grants and company API keys?

An OAuth token belongs to the user who approved your app, and extra scopes such as call history, recaps and refresh need Dialpad's approval. A company API key is created by a Company Admin and can act across all users in the company, so an agent holding one can place calls and send texts as any rep.

What are Dialpad's API rate limits?

The API allows 20 requests per second per company, with tighter per-endpoint limits: 100 SMS per minute, 5 call initiations per minute per user and 12 AI Recap reads per minute. MCP tool calls count against the same limits, so a chatty assistant can throttle a customer's other integrations.

How does Scalekit connect an AI agent to Dialpad?

Scalekit's Dialpad connector runs per-user OAuth through your own Dialpad OAuth app, stores each rep's tokens in a vault and refreshes them, so the agent never handles them. It exposes 112 tools, 68 read, 42 write and 2 destructive, which you narrow per agent role and serve through a virtual MCP server.

Build your Dialpad agent with Scalekit

Browse the Scalekit Dialpad connector and the Dialpad connector docs with the full tool list. See Scalekit pricing, or talk to us.

No items found.
On this page
Share this article
Scalekit
Find us on ProductHunt

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.