Tutorial

MCP Tool Call Example

One real tool call, end to end: the request that dials a number, the response you get back, how to poll for the outcome, how to fetch the transcript, and what it looks like when something goes wrong instead of when it works.

Connect your agentRead the docs

1. The request

A tools/call is a JSON-RPC 2.0 POST with a bearer token. The body names the tool and supplies arguments matching its schema:

POST /mcp
Authorization: Bearer kc_live_...
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "make_call",
    "arguments": {
      "agent_id": "agt_9f2a1c",
      "to": "+15551234567",
      "context": "Confirm Thursday 2pm appointment"
    }
  }
}

2. The response

The tool result is wrapped in the JSON-RPC envelope under result. It carries a text summary for a model to read and a structuredContent object for code:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Call initiated successfully.\nCall ID: call_3f9a1e2b\nStatus: queued" }
    ],
    "structuredContent": {
      "success": true,
      "call": {
        "id": "call_3f9a1e2b",
        "conversation_id": "conv_7c81ff90",
        "status": "queued",
        "agent_id": "agt_9f2a1c",
        "to": "+15551234567"
      }
    }
  }
}

The call is placed at this point — the response confirms only that dialing started; the call itself may still be in progress. Save call.id.

3. Poll for status

Call check_call_status with that ID until you get a terminal status. Status mirrors what the telephony carrier reports for the call, so expect the same vocabulary any carrier uses — queued, ringing, in-progress, then a terminal state like completed, no-answer, busy, failed, or canceled.

{ "name": "check_call_status", "arguments": { "call_id": "call_3f9a1e2b" } }
{
  "success": true,
  "call": {
    "id": "call_3f9a1e2b",
    "status": "in-progress",
    "duration_seconds": null,
    "agent_name": "Front Desk",
    "summary": null,
    "recording_url": null,
    "created_at": "2026-07-09T14:02:11Z"
  }
}

Call again a few seconds later. Once status is terminal, move on to the transcript.

4. Fetch the transcript

{ "name": "get_transcript", "arguments": { "call_id": "call_3f9a1e2b" } }
{
  "success": true,
  "call": {
    "id": "call_3f9a1e2b",
    "agent_name": "Front Desk",
    "status": "completed",
    "duration_seconds": 94,
    "summary": "Confirmed Thursday 2pm appointment.",
    "recording_url": "https://.../call_3f9a1e2b.mp3"
  },
  "transcript_available": true,
  "transcript": "AI: Hi, this is Front Desk calling to confirm...\nHuman: Yes, that works...",
  "transcript_truncated": false,
  "transcript_length": 812
}

Transcripts over 50,000 characters come back truncated, with transcript_truncated: true and the untruncated transcript_length so you know how much was cut.

Failure modes

Three different things can go wrong, and each one looks different in the response.

Call didn't connect / no answer

make_call still returns success — the call was placed. The failure shows up later, when check_call_status reports a terminal status other than completed (no-answer, busy, failed). get_transcript at that point returns transcript_available: false with the reason in the call summary if the carrier provided one.

Provider rejected the call

make_call itself fails before dialing — an unverified caller ID, for example — and the response is an error result:

{
  "content": [{ "type": "text", "text": "Error: Outbound calling is pending carrier verification for this caller ID — ..." }],
  "structuredContent": {
    "success": false,
    "error": "Outbound calling is pending carrier verification for this caller ID — ...",
    "blocked_by": "caller_reputation",
    "agent_id": "agt_9f2a1c",
    "to": "+15551234567"
  },
  "isError": true
}

Unexpected server error

If the handler throws instead of returning a controlled error, the HTTP status is 500 but the JSON-RPC envelope still carries a normal result — with isError: true — rather than an empty response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "Error: <message>" }],
    "structuredContent": { "success": false, "error": "<message>", "tool": "make_call" },
    "isError": true
  }
}

Bad request at the protocol level

Calling a tool name that doesn't exist, or a real tool without the scope your token was granted, fails before the handler ever runs, as a standard JSON-RPC error instead of a tool result:

{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Unknown tool: dial_number" } }

Every path is checkable the same way: read isError / structuredContent.success for tool-level failures, and the top-level error field for protocol-level ones. Nothing silently returns an empty transcript when the real answer is "this failed."

Connect your agentRead the docsNew to MCP? Start here
Connect