Skip to main content
Glama
skbhati199

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.