Connect AI Agents via MCP
Threll's MCP server lets AI agents operate your voice workforce conversationally: "Have Harald call these five leads tomorrow at 9" becomes a prepared, confirmed, scheduled batch — no REST plumbing on your side.
What an agent can do
With the mcp:account_admin scope, a connected agent can manage workers, place and schedule calls (with human confirmation), search call history and transcripts, manage call-script documents, and check telephony status. With mcp:tools:<integration> scopes, it can also act through your connected integrations — book in Cal.com or EasyPractice, look up Magento orders, and more. The full catalog is in the MCP reference.
Connect from Claude
Add the connector
In Claude's settings, add a custom connector with the server URL https://mcp.threll.io. Claude discovers the OAuth configuration automatically and registers itself as a client — no manual credentials.
Approve access
You're sent to the Threll consent page, where you sign in, pick the account, and approve the requested scopes. Grant the minimum the agent needs — scopes can't be escalated later without re-consent.
Use the tools
Threll's tools appear in Claude's tool list, filtered to your granted scopes. The server also ships usage instructions with the catalog, so the agent knows to check telephony status before dialing and how to handle confirmations.
Any other MCP-compatible client works the same way — the server uses standard OAuth 2.1 discovery, dynamic client registration, and the Streamable HTTP transport.
The call confirmation flow
Placing phone calls is the one destructive action an agent can take against the outside world, so it's gated. By default (account setting require_confirmation_for_outbound_calls, on unless disabled), agent-initiated calls are a two-step handshake:
{
"status": "confirmation_required",
"confirmation_token": "conf_9b3e...",
"summary": {
"worker": "Harald",
"from": "+4721000000",
"to": "+15555550100",
"scheduled_at": "2026-06-12T09:00:00Z",
"instructions_preview": "Renewal reminder, contract expires..."
}
}The agent presents the summary — who's calling, from which number, to whom, when, and with what instructions — and only after the user agrees does it call confirm_phone_call with the token. The confirmation is consumed atomically: a token can only ever place one call.
{
"name": "confirm_phone_call",
"arguments": { "confirmation_token": "conf_9b3e..." }
}Useful companions:
prepare_phone_call— same handshake, but explicit: drafts without dialing even when the account toggle is off.prepare_phone_call_batch/confirm_phone_call_batch— up to 100 calls validated and confirmed as one unit, then drained by the scheduler.cancel_pending_confirmation— revoke a token the user decided against.get_outbound_call_policy— read the toggle and any pending confirmations.
Build your own client
If you're not using an off-the-shelf MCP client, the flow is standard OAuth 2.1 with PKCE followed by JSON-RPC calls:
const MCP = 'https://mcp.threll.io';
// 1. One-time: register your client (public, PKCE-only)
const reg = await fetch(`${MCP}/oauth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_name: 'My Agent',
redirect_uris: ['https://my-agent.example.com/callback'],
}),
}).then(r => r.json());
// 2. Send the user to the consent page with a PKCE challenge,
// then exchange the returned code for tokens:
const tokens = await fetch(`${MCP}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: authCode,
redirect_uri: 'https://my-agent.example.com/callback',
client_id: reg.client_id,
code_verifier: pkceVerifier,
}),
}).then(r => r.json());
// 3. Call tools over JSON-RPC (or the REST /tools endpoints)
const result = await fetch(MCP, {
method: 'POST',
headers: {
'Authorization': `Bearer ${tokens.access_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: 'list_workers', arguments: {} },
}),
}).then(r => r.json());import requests
MCP = "https://mcp.threll.io"
# 1. One-time: register your client (public, PKCE-only)
reg = requests.post(f"{MCP}/oauth/register", json={
"client_name": "My Agent",
"redirect_uris": ["https://my-agent.example.com/callback"],
}).json()
# 2. Send the user to the consent page with a PKCE challenge,
# then exchange the returned code for tokens:
tokens = requests.post(f"{MCP}/oauth/token", json={
"grant_type": "authorization_code",
"code": auth_code,
"redirect_uri": "https://my-agent.example.com/callback",
"client_id": reg["client_id"],
"code_verifier": pkce_verifier,
}).json()
# 3. Call tools over JSON-RPC (or the REST /tools endpoints)
result = requests.post(MCP,
headers={"Authorization": f"Bearer {tokens['access_token']}"},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "list_workers", "arguments": {}},
},
).json()Access tokens last 1 hour; refresh with grant_type=refresh_token (valid 30 days). If you're embedding tools straight into an LLM instead of speaking MCP, the REST endpoints return the catalog pre-formatted for Claude, OpenAI, or Gemini.