AIがTwilioを操作する！Twilio MCPを試してみた


みなさんは、MCPを利用していますか？
遅ればせながら、TwilioからTwilio MCPという機能が公開されたので書いていきたいと思います。
すでに英語で書かれたBlogが[Twilio Blog](https://www.twilio.com/en-us/blog/introducing-twilio-alpha-mcp-server)で紹介されています。
なので、ここでは私の観点で[Twilio MCP](https://twilioalpha.com/mcp "Twilio MCP")に触れていきたいと思います。

## Twilio MCPとは

Twilio MCPは、Model Context Protocol（MCP）を活用したTwilioの新しいサービスです。MCPとは、Anthropicが設計した新しいオープンスタンダードで、AIモデルと外部ツール・情報源との連携方法を標準化するプロトコルです。
簡単に言えば、MCPは「AIの能力拡張プロトコル」と考えることができます。AIモデル（例：Claude）が外部のサービス（例：Twilio）と効率的に対話するための共通言語を提供します。

### Twilio MCPの特徴

Twilio MCPサーバーは、このプロトコルを採用して、TwilioのコミュニケーションAPIとAIモデルを直接つなぐサービスです。主な特徴は以下の通りです：

1. **標準化されたインターフェース**: 開発者はTwilioの個々のAPIの詳細を理解しなくても、AIモデルを通じて機能を活用できます
2. **コンテキスト管理の自動化**: ユーザーの意図やコミュニケーションの文脈を理解し、適切なAPI呼び出しに変換します
3. **スケーラビリティ**: 1つのMCPサーバーで多様なTwilio機能（SMS、音声通話、電話番号管理など）にアクセスできます
4. **セキュリティの向上**: センシティブな認証情報やAPIキーをAIモデルに直接渡す必要がなく、MCPサーバーが安全に管理します

## Twilio MCPとTwilio APIの違い
従来のTwilio APIの利用方法と比較すると、以下のような違いがあります：

### 従来のTwilio API利用：
```javascript
// 開発者が明示的にコードを書く必要がある
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);

client.messages
  .create({
    body: 'こんにちは、これはテストメッセージです',
    from: '+1XXXXXXXXXX',
    to: '+81XXXXXXXXXX'
  })
  .then(message => console.log(message.sid));
```

### Twilio MCPを使った場合：

```
// AIエージェントに自然言語で指示できる
「+81XXXXXXXXXXに『こんにちは、これはテストメッセージです』というメッセージを送信してください」
```

AIエージェントがこの指示を解釈し、適切なTwilio APIを呼び出すためのリクエストをMCPサーバーに送信します。MCPサーバーは、そのリクエストを実際のTwilio APIコールに変換し実行します。

## Twilio MCPのセットアップ
こちらのBlogを見てセットアップしてみてください。
わかりにくいなどのリクエストがあれば、私の方で後日手順を書きたいと思います。

https://www.twilio.com/en-us/blog/introducing-twilio-alpha-mcp-server

## 実際に色々話しかけてみた
ここでは私が実際に動作確認をした例をご紹介します。

### 1. Twilioの電話番号一覧取得
```
私：私が持っている電話番号を教えて
```
```
AI：Twilioアカウントに登録されている日本の電話番号を確認します。
前回の検索では以下の日本の電話番号が確認できました：

1. `+81XXXXXXXXXX` - 2024年6月6日に作成
2. `+81XXXXXXXXXX` - 「Live Translation #2」という名前で2025年3月6日に作成
3. `+81XXXXXXXXXX` - 「Live Translation #1」という名前で2025年3月6日に作成
4. `+81XXXXXXXXXX` - 2024年12月31日に作成
5. `+81XXXXXXXXXX` - 2024年7月4日に作成

これらはすべて「+81」で始まる日本の電話番号です。
すべての番号が「50」から始まっていることから、
これらはおそらく050番号（IP電話番号）であると思われます。
特に「Live Translation」という名前が付いている2つの番号は、
翻訳サービスに関連して使用されているようです。
```
このようにMCPサーバとの接続が完了していると簡単に自分のTwilioアカウントの情報を取得できます。


### 2. SMSの送信
次に一番やりたかった、AIからのSMS送信を試しました。
送信するメッセージもAIに作ってもらって、あとは番号だけを指定して送るということができるかを検証しています。

```
私：お客様に忘れ物の連絡をSMSでしたいです。SMSのメッセージは適切な文章を考えて送ってほしい
```

```
AI：忘れ物の連絡をSMSで送信したいとのことですね。適切なメッセージを作成し、送信いたします。
まず、送信先のお客様の電話番号が必要です。また、送信元として使用する電話番号も選択する必要があります。

SMSの内容は以下のように作成しました：
【お忘れ物のお知らせ】
お客様、本日ご来店いただきありがとうございました。
お忘れ物がございましたのでご連絡いたしました。
営業時間内（10:00-19:00）にお立ち寄りいただくか、
ご都合が悪い場合はこのメッセージにご返信ください。お忘れ物はお取り置きしております。
お客様の電話番号をお知らせいただければ、このメッセージを送信できます。
また、メッセージの内容に修正や追加が必要な場合は、お知らせください。

送信元としては、過去のログで日本の電話番号への送信実績が多い `+1XXXXXXXXXX` を使用するのが
よいかと思いますが、日本の電話番号（+81で始まる番号）からの送信をご希望でしたら、そちらも選択可能です。
```

```
私：宛先は+81XXXXXXXXXXです。 送信元は、TWILIOTEST でお願いします
```

```
AI:承知しました。宛先番号 `+81XXXXXXXXXX` にTWILIOTESTを送信元として、忘れ物に関するメッセージを送信します。
CreateMessage---QKLUo2-W

リクエスト
``{ `To`: `+81XXXXXXXXXX`, `Body`: `【お忘れ物のお知らせ】 お客様、本日ご来店いただきありがとうございました。お忘れ物がございましたのでご連絡いたしました。営業時間内（10:00-19:00）にお立ち寄りいただくか、ご都合が悪い場合はこのメッセージにご返信ください。お忘れ物はお取り置きしております。`, `From`: `TWILIOTEST`, `AccountSid`: `ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` }``

レスポンス
`{ "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "api_version": "2010-04-01", "body": "【お忘れ物のお知らせ】\nお客様、本日ご来店いただきありがとうございました。お忘れ物がございましたのでご連絡いたしました。営業時間内（10:00-19:00）にお立ち寄りいただくか、ご都合が悪い場合はこのメッセージにご返信ください。お忘れ物はお取り置きしております。", "date_created": "Sat, 10 May 2025 04:24:02 +0000", "date_sent": null, "date_updated": "Sat, 10 May 2025 04:24:02 +0000", "direction": "outbound-api", "error_code": null, "error_message": null, "from": "TWILIOTEST", "messaging_service_sid": null, "num_media": "0", "num_segments": "2", "price": null, "price_unit": "USD", "sid": "SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "status": "queued", "subresource_uris": { "media": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Media.json" }, "to": "+81XXXXXXXXXX", "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json" }`

メッセージが正常に送信キューに入りました。

送信詳細：
- 送信先: +81XXXXXXXXXX
- 送信元: TWILIOTEST
- メッセージID: SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- ステータス: queued（送信待ち）
- 送信日時: 2025年5月10日 04:24:02 (UTC)
- セグメント数: 2

メッセージは現在「queued」（送信待ち）の状態です。
通常、すぐに「送信中」から「送信済み」の状態に変わります。
日本の携帯電話キャリアのネットワーク状況によっては、数分かかる場合もあります。
```
***実際に届いたSMSのスクショ***
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649595/91f074e6-40c1-4f7b-a3e8-90088625c28f.png" width="40%">

文章の生成から送信までAIとのチャットのやり取りだけで完結できました。

### 3. 電話をかける
オートコールの機能が実現できるのか試してみました。
こちらはTwilio MCPに実装されている`Create Call`で動作しています。
```
私：私の顧客リストに対して、電話をかけて欲しいです。
```

```
AI：お客様のリストに対して電話をかけたいとのことですね。Twilioを使って自動的に電話をかけることができます。
以下の情報が必要になります：

1. 電話をかける顧客リスト（電話番号のリスト）
2. 発信元として使用する電話番号（Twilioに登録されているもの）
3. 電話がつながった時に再生する音声メッセージやTwiMLスクリプト

まず、顧客リストはどのように提供されていますか？電話番号のリストをお持ちでしょうか、
それともデータベースやファイルからリストを取得する必要がありますか？

また、電話がつながった際にどのようなメッセージを流したいかについても教えていただけますと幸いです。
例えば：
- 録音した音声メッセージの再生
- テキスト読み上げ（Text-to-Speech）によるメッセージ
- オペレーターへの転送
- インタラクティブな自動応答システム（IVR）
これらの情報をもとに、顧客へ自動電話をかけるプロセスをセットアップすることができます。
```

```
私：顧客リストは、下記です。 
中村様 : +81XXXXXXXXXX 池田様：+81XXXXXXXXXX

メッセージは下記です。 もしもし。こちらはサービスセンターです。
本日は新しいキャンペーンのお知らせお電話させて頂きました。1分ほどのお時間でご案内させて頂きます。
```

```
AI：承知しました。中村様と池田様へ指定のメッセージで電話をかけます。
Twilioを使用して1件ずつ電話をかけていきます。

まず中村様（+81XXXXXXXXXX）に電話をかけてみましょう：
CreateCall---gykaX2sR

リクエスト
``{ `To`: `+81XXXXXXXXXX`, `From`: `+1XXXXXXXXXX`, `Twiml`: `<?xml version=\"1.0\" encoding=\"UTF-8\"?> <Response> <Say language=\"ja-JP\">もしもし。こちらはサービスセンターです。本日は新しいキャンペーンのお知らせお電話させて頂きました。1分ほどのお時間でご案内させて頂きます。</Say> <Pause length=\"2\"/> <Say language=\"ja-JP\">ご質問がありましたら、オペレーターにおつなぎいたします。おつなぎする場合は1を、終了する場合は2を押してください。</Say> <Gather numDigits=\"1\" timeout=\"10\"/> </Response>`, `AccountSid`: `ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` }``

レスポンス
`{ "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "annotation": null, "answered_by": null, "api_version": "2010-04-01", "caller_name": null, "date_created": null, "date_updated": null, "direction": "outbound-api", "duration": null, "end_time": null, "forwarded_from": null, "from": "+1XXXXXXXXXX", "from_formatted": "(928) 248-2888", "group_sid": null, "parent_call_sid": null, "phone_number_sid": "PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "price": null, "price_unit": "JPY", "queue_time": "0", "sid": "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "start_time": null, "status": "queued", "subresource_uris": { "events": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Events.json", "notifications": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json", "payments": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Payments.json", "recordings": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json", "siprec": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Siprec.json", "streams": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Streams.json", "transcriptions": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Transcriptions.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/UserDefinedMessageSubscriptions.json", "user_defined_messages": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/UserDefinedMessages.json" }, "to": "+81XXXXXXXXXX", "to_formatted": "+81XXXXXXXXXX", "trunk_sid": null, "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json" }`

中村様への通話がキューに入りました。続いて池田様（+81XXXXXXXXXX）に電話をかけます：

CreateCall---gykaX2sR

リクエスト

``{ `To`: `+81XXXXXXXXXX`, `From`: `+1XXXXXXXXXX`, `Twiml`: `<?xml version=\"1.0\" encoding=\"UTF-8\"?> <Response> <Say language=\"ja-JP\">もしもし。こちらはサービスセンターです。本日は新しいキャンペーンのお知らせお電話させて頂きました。1分ほどのお時間でご案内させて頂きます。</Say> <Pause length=\"2\"/> <Say language=\"ja-JP\">ご質問がありましたら、オペレーターにおつなぎいたします。おつなぎする場合は1を、終了する場合は2を押してください。</Say> <Gather numDigits=\"1\" timeout=\"10\"/> </Response>`, `AccountSid`: `ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` }``

レスポンス

`{ "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "annotation": null, "answered_by": null, "api_version": "2010-04-01", "caller_name": null, "date_created": null, "date_updated": null, "direction": "outbound-api", "duration": null, "end_time": null, "forwarded_from": null, "from": "+1XXXXXXXXXX", "from_formatted": "(928) 248-2888", "group_sid": null, "parent_call_sid": null, "phone_number_sid": "PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "price": null, "price_unit": "JPY", "queue_time": "0", "sid": "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "start_time": null, "status": "queued", "subresource_uris": { "events": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Events.json", "notifications": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json", "payments": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Payments.json", "recordings": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json", "siprec": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Siprec.json", "streams": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Streams.json", "transcriptions": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Transcriptions.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/UserDefinedMessageSubscriptions.json", "user_defined_messages": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/UserDefinedMessages.json" }, "to": "+81XXXXXXXXXX", "to_formatted": "+81XXXXXXXXXX", "trunk_sid": null, "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json" }`

両方のお客様への電話がキューに入りました。各通話の詳細は以下の通りです：

### 中村様 (+81XXXXXXXXXX)
- 通話ID: CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- ステータス: queued（発信待ち）
- 発信元: +1XXXXXXXXXX

### 池田様 (+81XXXXXXXXXX)
- 通話ID: CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- ステータス: queued（発信待ち）
- 発信元: +1XXXXXXXXXX

両方の通話では、以下のメッセージをTwiMLスクリプトで設定しました：

1. 「もしもし。こちらはサービスセンターです。本日は新しいキャンペーンのお知らせお電話させて頂きました。
1分ほどのお時間でご案内させて頂きます。」という日本語のメッセージを再生
2. 2秒間の間を置く
3. 「ご質問がありましたら、オペレーターにおつなぎいたします。おつなぎする場合は1を、終了する場合は2を押してください。」と続けて案内
4. ユーザーの入力待ち（1キーまたは2キーの入力を10秒間待機）

通話のステータスは、通常数秒後に「ringing」（呼び出し中）に変わり、相手が応答すると
「in-progress」（通話中）になります。通話の結果を確認したい場合は、
これらの通話IDを使用して後ほどステータスを確認することができます。
```

無事に電話がかかってきて、音声が流れ始めました。
驚くのはAI側で、ただメッセージを流すだけでなく、相手側のリアクションまでハンドリングしてくれるTwiMLスクリプトが出てきた点です。

## 実践的なユースケース
Twilio MCPは様々なシナリオで活用できるのではないかと考えています

1. **カスタマーサポートの自動化**：問い合わせを受け、適切な応答を生成し、必要に応じてSMSや電話で回答
2. **予約確認システム**：レストランや医療機関での予約確認と変更をAIが自動処理
3. **マルチチャネルマーケティング**：一度の指示でSMS、メール、音声など複数のチャネルでキャンペーンを展開
4. **緊急通知システム**：災害時などに多数のユーザーへ迅速に情報を配信

これらのユースケースでは、従来なら複雑なコードが必要でしたが、MCPによって開発の敷居が大幅に下がるのではないかと考えています。
みなさんも一度Twilio MCPを導入して色々と試して頂ければとおもいます。

注意）Twilio MCPは**Developer Preview**という形でBeta版の扱いになっております。