Security
The MCP server's only authentication is a JSON Authorization header carrying your Exotel API credentials. Treat it like any long-lived API secret. Keep it out of source control. Give each agent only the products it needs.
Authentication model
The MCP server itself performs no OAuth handshake and issues no MCP-specific tokens. Every request to mcp.exotel.com/mcp carries a single Authorization header whose value is a JSON object (not HTTP Basic Auth, not a Bearer token) holding your Exotel API credentials:
Authorization: {"token":"BASE64_OF_APIKEY_APITOKEN","from_number":"...","caller_id":"...","account_sid":"...","api_domain":"...","exotel_portal_url":"...", "voicebot_api_key":"...","voicebot_api_token":"...","voicebot_account_id":"...","cqa_api_key":"...","cqa_account_id":"..."}
On every request, the server parses the JSON and forwards the relevant credentials to the underlying CPaaS, VoiceBot, or CQA API. Each of those APIs enforces its own authentication. No session is stored server-side.
What the auth model does not do
Three limits are worth knowing before you deploy this in production:
- No OAuth today. Auth is the JSON
Authorizationheader. The MCP specification (2025-06-18) defines OAuth 2.1 for HTTP MCP when a server uses the spec's authorization profile. Exotel MCP does not implement that profile. - No scopes or per-tool permissions. A valid header can call every tool for the products it configures. If you need "SMS-send only" or "read-only VoiceBot" access, use separate Exotel API keys per agent, or separate accounts for dev and prod.
- Revocation means rotation. There is no "disconnect this integration" button. To invalidate a leaked header, rotate the underlying API keys at my.exotel.com and update every client that used them.
Product-scoped credentials
Each field belongs to one product. Omit any product you don't need. Its tools still show up in tools/list so your agent can see the full menu, but calling one returns a credential error.
| Product | Fields | Where to find them |
|---|---|---|
| CPaaS (voice, SMS, number lookup, Engage) | token, account_sid, from_number, caller_id, api_domain, exotel_portal_url | my.exotel.com → Settings → API Settings + Numbers |
| VoiceBot | voicebot_api_key, voicebot_api_token, voicebot_account_id, voicebot_base_url, calls_api_key, calls_api_token, calls_account_id | VoiceBot Dashboard → Settings → API Keys |
| Conversational Intelligence | cqa_api_key, cqa_account_id, cqa_host | CQA Console → Settings → API Keys |
Confirm what your header configures at any time by calling exotel_setup_guide.
Where credentials live on each client
The Authorization header has to travel with every MCP request, but you can choose where the JSON sits at rest on your disk. From strongest to weakest:
- OS keychain. The token is encrypted at rest and referenced from client config as an environment variable.
- Sourced dotenv file. The token lives in a
chmod 600file outside every git repo, sourced by your shell at login. - Inline in client config. The token sits directly in
mcp.json. This works, but the config file itself is now a secret. - Inline in
args. Avoid this entirely. The token leaks tops aux, crash dumps, and shell history the momentnpxspawns.
Recommended: OS keychain + shell env
Store the JSON in your login keychain and expose it as one environment variable at shell start. The client config file then carries no secret and is safe to commit.
On macOS, add the entry once:
security add-generic-password -a "$USER" -s exotel-mcp-auth \
-w '{"token":"BASE64_OF_APIKEY_APITOKEN","from_number":"YOUR_NUMBER","caller_id":"YOUR_NUMBER","account_sid":"YOUR_SID","api_domain":"https://api.in.exotel.com","exotel_portal_url":"https://my.exotel.com"}'
Export it from ~/.zshrc or ~/.bashrc:
export EXOTEL_AUTH_HEADER="$(security find-generic-password -a "$USER" -s exotel-mcp-auth -w)"
Then point each client at that variable. The syntax is not the same everywhere.
Cursor (~/.cursor/mcp.json) and VS Code (.vscode/mcp.json) expand ${env:NAME} in HTTP headers. Cursor documents this under config interpolation. VS Code uses the same token.
Cursor:
{
"mcpServers": {
"exotel": {
"url": "https://mcp.exotel.com/mcp",
"headers": { "Authorization": "${env:EXOTEL_AUTH_HEADER}" }
}
}
}
VS Code:
{
"servers": {
"exotel": {
"type": "http",
"url": "https://mcp.exotel.com/mcp",
"headers": { "Authorization": "${env:EXOTEL_AUTH_HEADER}" }
}
}
}
Restart the app after you change your shell profile. GUI apps do not pick up a new export until you relaunch them from that shell, or from the Dock after a logout.
Claude Code runs in a terminal, so it inherits EXOTEL_AUTH_HEADER. Add the server with the expanded header:
claude mcp add --transport http exotel https://mcp.exotel.com/mcp \
--header "Authorization: ${EXOTEL_AUTH_HEADER}"
Claude Desktop and Windsurf do not expand ${EXOTEL_AUTH_HEADER} inside mcp.json. A Dock-launched app also does not load ~/.zshrc. Use a wrapper that reads the keychain, then execs mcp-remote:
#!/bin/sh
set -e
AUTH_HEADER="$(security find-generic-password -a "$USER" -s exotel-mcp-auth -w)"
exec npx -y mcp-remote https://mcp.exotel.com/mcp --header "Authorization:${AUTH_HEADER}"
Save it as ~/.local/bin/exotel-mcp.sh, then chmod +x ~/.local/bin/exotel-mcp.sh. Point the client at the script:
{
"mcpServers": {
"exotel": {
"command": "/Users/YOU/.local/bin/exotel-mcp.sh"
}
}
}
Do not put "AUTH_HEADER": "${EXOTEL_AUTH_HEADER}" in the client's env block. Claude Desktop and Windsurf pass that string through as a literal.
Non-macOS equivalents for the keychain step:
- Linux. Use
pass(pass insert exotel-mcp-auth) orgnome-keyringviasecret-tool. In the wrapper, replace thesecurityline withAUTH_HEADER="$(pass show exotel-mcp-auth)". - Cross-platform. Use the 1Password CLI (
op read 'op://Private/Exotel MCP/notesPlain') or Bitwarden CLI (bw get notes exotel-mcp-auth).
Alternative: sourced dotenv file
If a keychain is not available, put the JSON in a chmod-600 file outside every git repo:
touch ~/.exotel-mcp.env && chmod 600 ~/.exotel-mcp.env
Contents of ~/.exotel-mcp.env:
export EXOTEL_AUTH_HEADER='{"token":"BASE64_OF_APIKEY_APITOKEN","from_number":"YOUR_NUMBER","caller_id":"YOUR_NUMBER","account_sid":"YOUR_SID","api_domain":"https://api.in.exotel.com","exotel_portal_url":"https://my.exotel.com"}'
Source it from your shell profile:
[ -f ~/.exotel-mcp.env ] && source ~/.exotel-mcp.env
The client config stays identical to the Cursor and VS Code examples above, with the same ${env:EXOTEL_AUTH_HEADER} references. For Claude Desktop and Windsurf, keep using the wrapper script. Point the script at the dotenv file instead of the keychain:
#!/bin/sh
set -e
. "$HOME/.exotel-mcp.env"
exec npx -y mcp-remote https://mcp.exotel.com/mcp --header "Authorization:${EXOTEL_AUTH_HEADER}"
Fallback: inline in client config
Every client accepts the JSON inline. Use this only when the two options above are not available, and treat the config file itself as a secret. Never commit it, screenshot it, or paste it into a support ticket.
| Client | Config file |
|---|---|
| Cursor | ~/.cursor/mcp.json (global) or .cursor/mcp.json (project) |
| VS Code | .vscode/mcp.json |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows) |
| Claude Code | .claude/settings.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
Inline example (Cursor):
{
"mcpServers": {
"exotel": {
"url": "https://mcp.exotel.com/mcp",
"headers": {
"Authorization": "{\"token\":\"BASE64_OF_APIKEY_APITOKEN\",\"from_number\":\"YOUR_NUMBER\",\"caller_id\":\"YOUR_NUMBER\",\"account_sid\":\"YOUR_SID\",\"api_domain\":\"https://api.in.exotel.com\",\"exotel_portal_url\":\"https://my.exotel.com\"}"
}
}
}
}
Add the config path to your global gitignore (~/.config/git/ignore) so it never lands in a repo by accident.
Never: inline in args
For mcp-remote-based clients, do not put the token directly in the argument list:
"args": ["mcp-remote", "https://mcp.exotel.com/mcp", "--header", "Authorization:{\"token\":\"...\"}"]
Once npx spawns mcp-remote, that argument shows up in ps aux, crash dumps, and shell history. Use one of the three patterns above instead.
Rotate credentials
- CPaaS. Regenerate the API key and token at my.exotel.com → Settings → API Settings. Recompute the base64
tokenfield withecho -n "NEW_API_KEY:NEW_API_TOKEN" | base64. Update every client's config file, then restart. - VoiceBot. Regenerate keys in the VoiceBot Dashboard → Settings → API Keys, update
voicebot_api_keyandvoicebot_api_token, then restart. - CQA. Call
exotel_cqa_create_api_keyto issue a new key andexotel_cqa_revoke_api_keyto disable the old one, then updatecqa_api_keyand restart.
Each product has independent keys, so you can rotate one without touching the others.
Least-privilege setup
Include only the products your agent actually needs. If your agent will only send SMS, drop the VoiceBot and CQA fields entirely. The tools still appear in tools/list but return credential errors, and no VoiceBot or CQA credentials sit in your config file.
For production or shared devices, use separate Exotel accounts (or restricted API credentials on the same account) for development and production. Never point an agent at production credentials while iterating on a prompt.
Human confirmation
Enable human confirmation of tool calls in your MCP client. Most clients (Cursor, Claude Desktop) can prompt before every tool invocation. This blocks the agent from placing a call, sending SMS, or spending money without your explicit approval.
Be careful when running the Exotel MCP server alongside other MCP servers that serve untrusted content. A malicious document from another server can carry instructions that try to trigger your Exotel tools; this is called prompt injection. Human confirmation is your main defense.
Data residency
Exotel's MCP server routes calls to the standard Exotel APIs: api.in.exotel.com or api.exotel.com, plus VoiceBot and CQA. Your data stays inside those product boundaries. The MCP server itself is a thin, stateless request router.
For India-hosted accounts, set api_domain to https://api.in.exotel.com so voice and SMS traffic terminates in Mumbai. For globally-hosted accounts, use https://api.exotel.com. VoiceBot and CQA endpoints follow the same pattern.
Reporting security issues
Email hello@exotel.com with the subject Security disclosure — MCP server.