Bring Your Own MCP Integration
This page covers the reverse direction: your systems act as MCP tools that our VoiceBot invokes during a live call.
If you want your AI client (Claude, Cursor, etc.) to make calls or send SMS through your Exotel account, see the Exotel MCP Server instead.
The Exotel VoiceBot platform uses MCP (Model Context Protocol) as the default way to integrate with external systems. Instead of building a custom integration for every platform (Salesforce, Freshdesk, etc.), you register MCP-compliant tools on the Exotel Tools Server, attach them to a specific assistant, and the bot invokes them during live calls.
How it works
┌─────────────┐ MCP ┌──────────────┐ API ┌──────────────┐
│ VoiceBot │ ──────────── │ Tools Server │ ──────────── │ Your System │
│ (AI Agent) │ │ │ │ (CRM, DB...) │
└─────────────┘ └──────────────┘ └──────────────┘
- VoiceBot receives a customer call and determines it needs external data
- Tools Server exposes your tool via MCP to the bot
- Your system receives the API call and returns data
- VoiceBot uses the response in the conversation
Prerequisites
Every curl on this page uses two placeholders. Both come from your Exotel account, not from Exotel-issued MCP keys:
| Placeholder | Where to get it |
|---|---|
<tenant_id> | Your Exotel account (tenant) ID. Find it in the VoiceBot Dashboard → Settings, or from the URL when you are logged into my.exotel.com. |
<your_token> | The access_token cookie your browser sets when you log into my.exotel.com. Open DevTools → Application → Cookies → my.exotel.com → copy access_token. |
access_token expires when you log out of my.exotel.com. Anyone who has it can act as you against the Tools Server until it expires. Do not commit it, paste it into tickets, or share it. The VoiceBot REST APIs use separate API keys (see Overview → VoiceBot). The Tools Server still authenticates with this cookie.
For host, use https://tools-server.mum1.exotel.com for production or https://tools-server-uat.mum1.exotel.com while testing (per the Tools Server overview). The examples below use the UAT host.
Which tool type do I pick?
| I want to... | Use | Why |
|---|---|---|
| Have the bot call an existing external API (CRM, order system, third party) | OpenAPI | Self-documenting, reusable, no code |
| Run custom logic with no existing API (EMI, tax, eligibility scoring) | Python | Inline function, typed parameters |
| Answer FAQ / policy / product questions from documents | Knowledge Base | RAG over your document corpus |
For the full comparison and worked schema for each type, see the Tools Server overview.
Create the tool
OpenAPI (recommended)
If your system already has an API, define it as an OpenAPI tool:
curl -X POST \
'https://tools-server-uat.mum1.exotel.com/tools/api/v1/tenants/<tenant_id>/tools' \
-H 'Content-Type: application/json' \
-H 'Cookie: access_token=<your_token>' \
-d '{
"name": "crm_lookup",
"description": "Look up customer details from CRM by phone number",
"type": "openapi",
"definition": {
"openapi": "3.0.0",
"info": { "title": "CRM API", "version": "1.0.0" },
"servers": [{ "url": "https://your-crm-api.example.com" }],
"paths": {
"/api/customers/lookup": {
"post": {
"summary": "Look up customer",
"operationId": "lookup_customer",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["phone_number"],
"properties": {
"phone_number": {
"type": "string",
"description": "Customer phone number"
}
}
}
}
}
},
"responses": {
"200": { "description": "Customer found" }
}
}
}
}
}
}'
Python
For custom logic that has no existing API:
curl -X POST \
'https://tools-server-uat.mum1.exotel.com/tools/api/v1/tenants/<tenant_id>/tools' \
-H 'Content-Type: application/json' \
-H 'Cookie: access_token=<your_token>' \
-d '{
"name": "calculate_emi",
"description": "Calculate monthly EMI for a loan amount",
"type": "python",
"definition": {
"body": "rate = annual_rate / 12 / 100\nemi = principal * rate * (1 + rate)**months / ((1 + rate)**months - 1)\nreturn round(emi, 2)",
"parameters": {
"principal": { "type": "number", "description": "Loan amount" },
"annual_rate": { "type": "number", "description": "Annual interest rate (%)" },
"months": { "type": "number", "description": "Loan tenure in months" }
},
"return_value": { "type": "number", "description": "Monthly EMI amount" }
}
}'
Knowledge base
For FAQ-style lookups backed by documents:
# Step 1: Create a knowledge corpus with your documents
curl -X POST \
'https://tools-server-uat.mum1.exotel.com/tools/api/v1/tenants/<tenant_id>/knowledgecorpora' \
-H 'Cookie: access_token=<your_token>' \
-F 'metadata={"name":"Product FAQ","description":"Product knowledge base","rag_engine":"vertex_ai"}' \
-F 'files=@product-faq.txt' \
-F 'files=@pricing.txt'
# Step 2: Create a knowledge base tool pointing to the corpus
curl -X POST \
'https://tools-server-uat.mum1.exotel.com/tools/api/v1/tenants/<tenant_id>/tools' \
-H 'Content-Type: application/json' \
-H 'Cookie: access_token=<your_token>' \
-d '{
"name": "product_faq",
"description": "Answer questions about our products and pricing",
"type": "knowledge_base",
"definition": {
"knowledge_corpus_id": "<corpus_id_from_step_1>"
}
}'
Test the tool
Before activating a tool, test it with sample inputs:
curl -X POST \
'.../tenants/<tenant_id>/tools/<tool_id>/versions/<version_id>/tests' \
-H 'Content-Type: application/json' \
-H 'Cookie: access_token=<your_token>' \
-d '{
"test_inputs": {
"phone_number": "+919876543210"
}
}'
Activate the tool
Once tested, activate it so VoiceBots can use it:
curl -X PUT \
'.../tenants/<tenant_id>/tools/<tool_id>' \
-H 'Content-Type: application/json' \
-H 'Cookie: access_token=<your_token>' \
-d '{
"status": "active",
"current_version_id": "<version_id>"
}'
Attach a tool to a voice assistant
Activating a tool does not automatically give every bot access to it. To let a specific assistant invoke your tool, update the assistant to attach it. The same update lets you decide where each tool parameter gets its value from.
Every parameter you declared on the tool must be filled from one of three sources:
| Source | Filled from | When to use |
|---|---|---|
| LLM | What the caller says on the phone. The bot's LLM extracts the value from the conversation. | Values only the caller can provide, such as order ID, complaint description, requested date. |
| Conversation state | Call metadata captured at session_start (caller ANI, CallSid, session id), earlier turns in the same call, or return values from prior tool calls. | Anything already known about the call, such as caller phone number, customer id returned by an earlier lookup, currently selected product. |
| Static | A fixed value baked into the assistant's tool config. | Constants that never change, such as API version, tenant identifier, channel name. |
Example: attaching crm_lookup to an assistant
For the crm_lookup tool created above (parameter: phone_number), attach it to an assistant and mark phone_number as sourced from conversation state, specifically the caller's ANI captured at session_start. The LLM never needs to ask the caller for it.
For a follow-up tool like get_order_details with parameters phone_number and order_id, source phone_number the same way (conversation state) and mark order_id as LLM so the bot asks the caller for it during the conversation.
For a tool like create_ticket that also needs channel and product_family, set those as static. Every ticket created by this assistant goes into the same channel and product family.
The exotel_voicebot_assistant_push_version MCP tool schema carries the exact payload shape. Your AI client fills it in for you once you tell it which source you want for each parameter.
How the bot decides to invoke a tool
The bot's underlying LLM sees your tool's name, description, and parameters schema. At each conversational turn, it decides whether the caller's current utterance matches a tool's purpose.
The description field is the primary signal. Write it in plain language, from the caller's perspective, stating both what the tool does and when to use it:
- Weak.
"crm lookup by phone" - Strong.
"Look up a customer's account details by phone number. Use when the caller asks about an order, wants to check their account, or identifies themselves."
If the bot never invokes your tool, the description is usually the culprit. The next most common causes are two tools with overlapping descriptions (the LLM cannot tell them apart) or a parameter the LLM cannot extract from natural speech (fix this by adding examples to the parameter description).
End-to-end example: a live call
Assume you have attached crm_lookup to your customer-support assistant with phone_number sourced from conversation state (the caller's ANI captured at session_start).
Caller (calls in from +919876543210)
Bot: Hi, this is Acme Support. How can I help you today?
Caller: I'd like an update on my order.
Between the two bot turns, this is what happens:
- The bot's LLM recognises the intent (the caller wants an order update) and picks
crm_lookupfrom the tools you attached to this assistant, matching on the tool'sdescription. - The
phone_numberparameter is filled from conversation state (+919876543210, captured at session start). The LLM does not need to ask the caller for it. - The bot invokes the tool. The endpoint your OpenAPI spec declared receives:
POST https://your-crm-api.example.com/api/customers/lookup
Content-Type: application/json
{ "phone_number": "+919876543210" }
- Your CRM responds:
{
"customer_id": "cust_1234",
"name": "Sarah",
"recent_orders": [
{ "order_id": "ord_A9F2", "status": "shipped", "eta": "2026-08-30" }
]
}
- The bot uses the response in its reply:
Bot: Hi Sarah, your order ord_A9F2 has shipped and should arrive on August 30. Is there anything else I can help you with?
The caller sees none of this. From your side, the tool is just an HTTP endpoint your CRM already had. The bot invokes it exactly the way your OpenAPI spec described, using the parameter sources you configured when attaching the tool.
Best practices
- Write clear descriptions. The LLM uses the
descriptionfield to decide when to invoke your tool. - Define typed parameters. This helps the LLM extract the right values from the conversation.
- Test before activating. Verify with the test endpoint first.
- Version your tools. Each update creates a new version. Activate only tested versions.
- Prefer OpenAPI tools for external APIs. They are self-documenting and reusable.