Skip to content

MCP server ​

DialCru serves its data to AI assistants over the Model Context Protocol. Connect an MCP client as yourself, by signing in (OAuth), or with an API key, and the client's own model can answer questions about your calls — no copying, no export.

The server is read-only. Anything that changes data — creating a campaign, tagging contacts, the do-not-call list — needs a person to approve it, and that approval cannot be enforced inside somebody else's client, so it stays in the dashboard's own assistant.

Connecting ​

SettingValue
URLhttps://<your DialCru host>/api/mcp
TransportStreamable HTTP
AuthOAuth 2.1, or authorization: Bearer <key> with an API key

The server is stateless: every request carries its credential, no session id is issued, and only POST is accepted (GET and DELETE answer 405). Responses are JSON, never a server-sent event stream.

As yourself (OAuth) ​

Add the URL as a custom connector in Claude, ChatGPT or any client that speaks MCP's authorization flow. The client registers itself, opens a browser to sign you in, and asks you to allow it on a consent page that names the client, where it will send you back to, what it may read, and which organisation it works in. Choosing an organisation there also makes it your active one in DialCru. The connection then reads only that organisation, and only what your role can read in the dashboard — contact details are masked exactly as they are for you.

Only roles that can use the dashboard's assistant (owner and admin) can connect. Your membership and role are checked on every request: leave the organisation or lose the role and the connection stops working, with nothing to revoke. An access token lasts an hour; the client refreshes it on its own.

Settings → MCP lists every app you have connected, in which organisation, and when it was last active. Disconnect stops it at once: its next request answers 401 even with an access token that has not expired, and its refresh token no longer works. To move an app to another organisation, disconnect it and connect it again.

For a client that discovers the server itself: a request without a credential answers 401 with www-authenticate: Bearer resource_metadata="https://<host>/.well-known/oauth-protected-resource/api/mcp" (RFC 9728), which names the authorization server, whose metadata is at https://<host>/.well-known/oauth-authorization-server (RFC 8414). Registration is open (RFC 7591), PKCE (S256) is required, and the authorization request must name the server as its resource (RFC 8707). The scope is mcp:read, plus offline_access for a refresh token.

A request under OAuth is rate-limited per person (120 a minute) and logged under Settings → API keys beside key requests, named OAuth · <your email> · <client id>.

With an API key ​

A request is an ordinary public-API request: it is authenticated, counted against the key's rate limit, and logged under Settings → API keys like any other. Errors before the MCP layer — a missing key, an exhausted limit — use the API's error envelope.

Tools ​

Over OAuth, a tool is listed when your role can read what it reads in the dashboard, so a connection gets every read tool the dashboard's assistant offers you — more than the table below.

With a key, a tool is listed only when the key holds its scope — except read_docs, which reads nothing of your organisation's and needs no scope, so every connection lists at least that one.

Each kind of record has a list_ tool and, where a record stands on its own, a get_ tool; where counting or comparing records is the question, it has a summarise_ tool too:

  • A list_ tool takes the same filters as that record's list page in Call Center, plus sort, limit (at most 25) and offset. It answers up to 25 rows, total (how many matched, so a count needs only limit: 1) and see_all, a link to the page with the same filters.
  • A get_ tool takes up to 10 record ids (call_ids, contact_ids, …; get_transcript takes up to 5 call_ids) and answers rows, the records found, with the same fields its list rows carry, and not_found, the ids that named none you may read. It is an error only when none was found.
  • Both take columns, a list of the fields to return. id and link always come back. Without it, a get returns every field; a list leaves out its long ones (a call's notes, a script's steps, a webhook's bodies), which columns names to include.
  • A summarise_ tool takes the same filters as its list_ tool, plus group_by (up to two dimensions, such as agent and day), measures, sort and limit (at most 60 groups). It answers one row per group with each measure, groups (how many there were), overall (the measures for everything that matched) and see_all. A row whose groups the list can filter by carries a link to those records. Rates are percentages (0–100, named _pct), durations are seconds, and days and hours are in the organisation's time zone. Grouping by day, week or month needs the tool's range (placed, queued, enrolled), and a series of days has a row for every day, zero when nothing happened. sort takes a measure (-sales) or the time dimension grouped by (-day is newest first). A range with more than 60 days is refused unless sort names a measure and limit is set: then every day is ranked and only the top limit returned ("the best five days this year"). A chart argument is ignored over MCP.
  • versus compares with an earlier range, on a summarise_ tool with a range: "previous" for as many days just before it, or its own from and to. Each row then also carries before (its measures over that range), change (now minus before; a rate's change is in percentage points) and change_pct (a count, duration or amount's change as a percent of before), and versus holds the same for overall. A group with nothing in the earlier range is compared with zero. sort takes a measure followed by _change, so -sales_change puts the biggest rise first. It needs the range, and cannot group by day, week or month, whose groups differ between the ranges.
    • summarise_calls measures calls as the Reports dashboard does: practice calls are left out. Its leads, reported, won, report_rate_pct, close_rate_pct and revenue_zar are what your CRM reported through lead results: won is a sale the CRM reported, where sales is a lead sent on. Grouping by lead_result splits calls into won, no_sale, open and unreported; caller_id is the number an outbound call showed; script_version the published script the agent was shown; agent_kind people or AI agents; ended_by who ended the call (customer, agent or platform); talk how long it was connected, in bands from not_connected and under_10s to 3m_plus. Each of these is also a list_calls filter. Its cost_zar, cost_per_dial_zar, cost_per_rpc_zar, cost_per_lead_zar (per lead sent), cost_per_sale_zar (per sale the CRM reported) and margin_zar (revenue less spend) count what your credit was charged for those calls — minutes, voice AI, transcription, QA review and disposition — so they leave out what no call carries, such as number rental and the assistant. talk_p50_sec, talk_p90_sec, acw_p50_sec and acw_p90_sec are the median and 90th percentile beside the averages. last_step groups calls by where their walk through the script stopped (Script · step, or end of script); people's presses are recorded and an AI agent's are not, so AI calls group apart as not recorded (AI call). lead_age groups campaign dials by how old the lead was when dialled (under_5m to 30d_plus), youngest first: the decay curve. talk_share groups by the agent's share of the talking (under_40 to 75_plus), and agent_talk_share_pct, avg_dead_air_sec, avg_longest_dead_air_sec, avg_overtalk and avg_longest_monologue_sec average the figures a call's page shows, over speech_calls: calls whose own recording was transcribed in stereo. last_step, lead_age, talk_share and objection are also list_calls filters.
    • summarise_script_steps follows people's calls on one script (script_id, optionally one version) under list_calls' filters: each step's reached, stopped_here and stop_rate_pct, handed_on_pct with its interval, median_sec_on_step, and ways_out (each button, jump or back, and how many took it). A step is its key across versions.
    • summarise_objections counts what customers pushed back with — price, not interested, already covered, trust and the rest — as each call's latest AI review read it, by objection, handled, resolved, rebuttal (the script objection the agent answered with) and the call's agent, campaign, outcome, script version or day. reviewed_calls is how many of the filter's calls were read at all; an AI review from before objections were read has none.
    • summarise_webhook_deliveries counts every endpoint's deliveries, a deleted endpoint's included, over the days they were queued. The endpoint page's test sends are left out unless include_tests is set. Its failure_rate_pct is over settled deliveries only (delivered or given up). It takes list_webhook_deliveries' filters.
    • summarise_campaign_contacts counts campaign rosters as a campaign's progress bar does: a claim that has lapsed counts as available. Archived campaigns (status completed) are left out unless status, show_completed or campaign_id names them. Its outcome is each contact's latest outcome on that campaign, so a contact is counted once however many calls it took. enrolled narrows to the contacts that joined on those days, and groups by the day, week, month, hour or weekday they joined. Its speed to lead is median_to_first_dial_sec and avg_to_first_dial_sec (joining to the first call placed), first_dial_within_5m_pct and median_to_first_answer_sec. Grouped by the week or month contacts joined, reached_1d_pct, reached_7d_pct, reached_30d_pct, lead_7d_pct and lead_30d_pct are each cohort's curve, counted only over contacts old enough for that age. settled_per_day, days_to_finish and days_to_try_all are the pace over the last 7 days and how long what is left takes at it.
  • get_call and get_campaign carry more. A call's script is its walk through its script — each step left and how, where it stopped, the answers captured and each disclosure owed — and last_step is where it stopped. A campaign's projection is when it will finish at its last 7 days' pace (finishes_on, a local date, null when nothing settled that week) and when every contact will have had a first dial (all_tried_by).
  • Rates say how sure they are. A rate measured over a sample (a connect rate, a close rate, an auto-fail rate) carries interval, its 95% interval in its own unit (overall_interval for everything that matched), and, when grouped, vs_rest: whether the group's rate is higher or lower than everything else that matched beyond chance, same when the difference could be noise, or too_few when there is too little to tell, corrected for how many rows are compared at once. With versus, significant says the same of each rate's and each count's move between the ranges; a count is compared per day, so ranges of different lengths are fair.
  • list_webhook_deliveries searches every endpoint's deliveries, or one's with webhook_id: by delivery_status, event, the days queued, the endpoint's last status_code, the call_id the event was about, and text in the body sent (sent) or in the endpoint's last answer (answered), any case. Test sends are left out unless include_tests is set. Before 2026-10-29 it read one endpoint's log only, and its status filter was named status.

What relates to a record is a filter on the other's list: a contact's calls are list_calls with contact, a campaign's contacts list_campaign_contacts.

ToolScopeWhat it returns
list_callscall:readCalls by number, SID, agent or people/AI, campaign, contact, status, outcome, day and hour placed, and what a summary groups by
get_callcall:readCalls by id: timing, agent, campaign, outcome, notes, legs, who hung up and what the CRM reported
summarise_callscall:readCalls counted, measured and costed by agent, people/AI, campaign, outcome, status, direction, caller ID, script version, lead result, who ended it, talk time or time
summarise_script_stepscall:readOne script's steps over people's calls: how many reached and stopped on each, handed a lead on after it, how long it held them, and where each press led
list_transcriptstranscript:readCalls whose transcripts match a full-text search, by agent, campaign, outcome, day placed and who said it, with the matching snippets
get_transcripttranscript:readWhat was said on calls, turn by turn, by call_ids
summarise_objectionscall:readWhat customers pushed back with on AI-reviewed calls, by objection, rebuttal, agent, campaign or outcome: how often answered and overcome, and whether the call still sold
list_contactscontact:readContacts by name, email, number, status, tag and custom field
get_contactcontact:readContacts by id: details, tags, custom fields and do-not-call status
list_contact_tagscontact:readThe organisation's contact tags and how many contacts carry each
get_contact_tagcontact:readContact tags by id
list_campaignscampaign:readCampaigns by name and status, with their progress
get_campaigncampaign:readCampaigns by id: status, progress, goal, calling window, script and caller ID
list_campaign_contactscampaign:readOne campaign's contacts by name, number, tag, dial state and latest outcome
get_campaign_contactcampaign:readCampaign contacts by id: attempts, last dial, latest outcome and notes, holder
summarise_campaign_contactscampaign:readRoster contacts counted by campaign, dial state, attempts, latest outcome or when they joined, with speed to lead
list_numbersnumber:readThe organisation's phone numbers, by number or label and health
get_numbernumber:readPhone numbers by id
list_webhookswebhook:readWebhook endpoints by name or URL, status, event and campaign
get_webhookwebhook:readWebhook endpoints by id, with their body templates (never their signing secrets)
list_webhook_deliverieswebhook:readDeliveries across endpoints by status, event, day, HTTP code, call, and text sent or answered
get_webhook_deliverywebhook:readDeliveries by id: their attempts, the body sent and the endpoint's answer and headers
summarise_webhook_deliverieswebhook:readDeliveries across endpoints, counted by endpoint, event, status, HTTP code or time
list_usage_eventsusage:readWhat the organisation was charged, one charge per row, by meter, day, campaign, contact, number and call
summarise_usage_eventsusage:readSpend summed by meter, AI feature, agent, campaign, contact, call, number or time, under the same filters

The do-not-call list is not readable with a key, as the REST API's suppressions are write-only, so a key cannot test numbers against it.

Source: ORG_TOOLS in src/lib/server/services/assistant/assistant_registry.ts, filtered to effect: "read" with a non-null scope. Tool names are stable; a renamed tool is a new tool.

With a key, phone numbers are returned in full, as the REST API returns them.

Documentation ​

read_docs reads one page of this documentation — the REST API, webhooks and this page — so a client's model can answer how something works (a scope, an error code, how to verify a signature) from the page rather than from memory.

It takes page, the page's path in this documentation, such as api/scopes.md; the tool's description lists every page with its title. It answers the page as a single markdown text block, with no structuredContent and no resource_link. A link in it to another page is rewritten to that page's page, to read with the same tool. A page that is not one is a tool error.

Source: AssistantDocs in src/lib/server/services/assistant/assistant_docs.ts. The tool's name is stable.

Results ​

Each result but read_docs's carries the same JSON twice:

  • structuredContent, the JSON as an object.
  • A text block holding the JSON as a string, for clients that predate structured content.

No tool declares an outputSchema; read a result's fields from the tool's description and the data itself.

A refusal — no such call, a range that is too long — is a tool error (isError: true) whose JSON, in both places, is { "error": "<sentence>", "code": "<code>" }, with codes from the API's error list.

A successful result also carries a resource_link to the page in Call Center that shows the same data — the call, or the call list with the same filters; a get_ that read several records carries one per record:

json
{
  "type": "resource_link",
  "uri": "https://<your DialCru host>/calls?status=ended",
  "name": "Calls",
  "title": "Calls",
  "mimeType": "text/html",
  "description": "25 of 340"
}

The link is for a person to open in a browser, where they sign in as usual. It is not an MCP resource: the server offers no resources/read.

Links inside results are paths on your DialCru host, such as /calls/<id>, but for those in a documentation page.

Untrusted text. Notes, transcripts and imported contact data were written by customers, agents or files, and arrive wrapped in <untrusted_content>…</untrusted_content>. The server's instructions tell the client's model to treat that text as data. Your client's model is yours: if it can act on other systems, review what it does with text from a call.

Prompts ​

The server offers ready-made questions as MCP prompts, the same ones the dashboard's assistant suggests. A client usually shows them as slash commands. Like tools, a prompt is listed only when the key holds a scope whose tools can answer it; a key with call:read gets all of these. With none, the server declares no prompts capability.

PromptArgumentAsks
week_vs_last_week—How did we do this week compared to last?
top_agents_yesterday—Which agents connected the most calls yesterday?
failed_calls_today—Show me today's failed calls
call_summarycall_idSummarise this call
call_endingcall_idWhy did this call end the way it did?
call_objectionscall_idWhat did the customer object to?
campaign_progresscampaign_idHow is this campaign going?
campaign_outcomescampaign_idWhat outcomes is this campaign getting?
campaign_agents_this_weekcampaign_idWhich agents worked this campaign this week?
agent_this_weekagent_idHow did this agent do this week?
agent_calls_todayagent_idShow this agent's calls today
agent_top_outcomesagent_idWhich outcomes does this agent file most?
ai_agent_script_syncai_agent_idIs this AI agent in sync with its script?
ai_agent_this_weekai_agent_idHow did this AI agent's calls go this week?
ai_agent_calls_todayai_agent_idShow this AI agent's calls today

An argument is the record's id, a UUID — as a tool returns it, or as the last segment of its page's URL. prompts/get answers one user message: the question, followed by the record's id and its link, which the client's model reads with the tools above. An id that is not a UUID is refused with JSON-RPC error -32602.

Prompt names are stable; a renamed prompt is a new prompt. Each prompt's title is the question, and its wording may change.

Saved prompts ​

An organisation's owners and admins can save questions of their own for the team. Each is listed beside the prompts above, named saved_ and its title in snake case (Weekly review is saved_weekly_review; a second title that reads the same takes _2). Its title is the title as saved, its description the start of its question, and prompts/get answers the question as written. A saved prompt tied to one kind of page takes that page's argument, as above (call_id for a call), and is listed only when the key can read that kind.

Unlike the prompts above, these names are not stable: they follow the organisation's titles, so renaming or deleting a saved prompt renames or removes it.