twilio-mcp-server
by skbhati199
README.md
# twilio-mcp-server
An MCP (Model Context Protocol) server that exposes **Twilio SMS sending** as MCP tools, protected by **OAuth 2.0 (client_credentials grant)**, and reachable remotely through a **Cloudflare Tunnel (cloudflared)**.
```
MCP Client --Bearer token--> [cloudflared tunnel] --> Express app :3020 --> Twilio API
|-- POST /oauth/token (get access token)
|-- POST /mcp (MCP tools, needs Bearer token)
|-- GET /health
```
## 1. Install
```bash
cd twilio-mcp-server
npm install
cp .env.example .env
```
Edit `.env`:
```env
PORT=3020
TRANSPORT=http
# OAuth 2.0 credentials for THIS server (make up your own strong values)
OAUTH_CLIENT_ID=demo-client-id
OAUTH_CLIENT_SECRET=demo-client-secret-change-me
OAUTH_JWT_SECRET=super-secret-signing-key-change-me
OAUTH_TOKEN_TTL_SECONDS=3600
# Twilio account credentials (from twilio.com/console)
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_FROM_NUMBER=+15017122661
```
## 2. Build & run
```bash
npm run build
npm start
```
You should see:
```
twilio-mcp-server v1.0.0
MCP endpoint: http://localhost:3020/mcp (requires Bearer token)
OAuth token: POST http://localhost:3020/oauth/token
Health check: GET http://localhost:3020/health
```
## 3. How the OAuth 2.0 flow works
This server implements the **client_credentials** grant (machine-to-machine — no user login screen):
1. A client calls `POST /oauth/token` with its `client_id` + `client_secret`.
2. The server verifies them and returns a signed, short-lived JWT `access_token`.
3. The client calls `POST /mcp` with `Authorization: Bearer <access_token>`.
4. The server verifies the token signature/expiry before handling any MCP request.
Get a token:
```bash
curl -X POST http://localhost:3020/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "demo-client-id",
"client_secret": "demo-client-secret-change-me"
}'
```
Response:
```json
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "sms:send"
}
```
Call the MCP endpoint with it:
```bash
curl -X POST http://localhost:3020/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer <access_token>" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
> Note: Twilio itself doesn't support OAuth for its SMS API — Twilio authenticates server-to-server using `TWILIO_ACCOUNT_SID` + `TWILIO_AUTH_TOKEN`. The OAuth 2.0 layer here protects **access to this MCP server**; the server then uses your Twilio credentials internally to actually send the SMS.
## 4. Tools exposed
| Tool | Description |
|---|---|
| `twilio_send_sms` | Sends an SMS. Args: `to` (E.164 phone number), `body` (message text, max 1600 chars) |
| `twilio_get_sms_status` | Looks up delivery status of a previously sent message by `message_sid` |
## 5. Expose it publicly with cloudflared (Cloudflare Tunnel)
Install `cloudflared` (one-time):
```bash
# macOS
brew install cloudflare/cloudflare/cloudflared
# Debian/Ubuntu
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# Windows
winget install --id Cloudflare.cloudflared
```
Start the MCP server (port 3020), then in a separate terminal run a **quick tunnel** (no Cloudflare account needed, great for testing):
```bash
cloudflared tunnel --url http://localhost:3020
```
Cloudflared prints a public URL like:
```
https://random-words-here.trycloudflare.com
```
Your MCP endpoint is now reachable at:
```
https://random-words-here.trycloudflare.com/mcp
```
and the OAuth token endpoint at:
```
https://random-words-here.trycloudflare.com/oauth/token
```
### Persistent named tunnel (production)
For a stable subdomain instead of a random one each time:
```bash
cloudflared tunnel login
cloudflared tunnel create twilio-mcp-server
cloudflared tunnel route dns twilio-mcp-server mcp.yourdomain.com
```
Create `~/.cloudflared/config.yml`:
```yaml
tunnel: twilio-mcp-server
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: mcp.yourdomain.com
service: http://localhost:3020
- service: http_status:404
```
Run it:
```bash
cloudflared tunnel run twilio-mcp-server
```
Now `https://mcp.yourdomain.com/mcp` is your permanent MCP endpoint.
## 6. Connect an MCP client through the tunnel
A ready-made example client is in `client-example/mcpClient.mjs`:
```bash
cd client-example
npm install
MCP_SERVER_URL=https://random-words-here.trycloudflare.com/mcp \
OAUTH_TOKEN_URL=https://random-words-here.trycloudflare.com/oauth/token \
OAUTH_CLIENT_ID=demo-client-id \
OAUTH_CLIENT_SECRET=demo-client-secret-change-me \
TO_NUMBER=+919876543210 \
MESSAGE_BODY="Hello from the tunnel!" \
node mcpClient.mjs
```
This will:
1. Fetch an OAuth access token from the tunnel URL
2. Connect to the MCP server through the tunnel
3. List available tools
4. Call `twilio_send_sms` to actually send a message
## 7. Testing with MCP Inspector
```bash
npx @modelcontextprotocol/inspector
```
Point it at `http://localhost:3020/mcp` (or your tunnel URL), set the `Authorization` header to `Bearer <token>` obtained from `/oauth/token`, and call the tools interactively.
## 8. Notes on Twilio trial accounts
If you're using a Twilio **trial** account:
- You can only send SMS to phone numbers you've **verified** in the Twilio console (Console → Phone Numbers → Verified Caller IDs).
- Messages will be prefixed with "Sent from your Twilio trial account".
- Upgrade the account to send to any number.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues