Skip to main content
Glama
mobilebiz

vonage-mcp-server

by mobilebiz

Vonage MCP Server

English | 日本語

VonageのSMS送信と音声通話を AIエージェントから安全に使うための MCP (Model Context Protocol) サーバーです。

利用者が自分の環境にコンテナを立て、自分の Vonage 資格情報を設定して使う OSS のリファレンス実装です。このプロジェクトがあなたの資格情報を預かることはありません。

このプロジェクトの前提

脅威モデル — エージェントを信頼できる主体として扱わない

攻撃者はプロンプトインジェクションによって AI エージェントを操れるものとします。

SMS 送信と音声通話は取り消せず、課金が発生し、相手にも迷惑がかかりうる操作です。「エージェントが正しく使えば安全」という前提は置けません。このサーバーのガードレールは、エージェントが敵対的に振る舞っても被害が上限内に収まることを目指しています。

そのため、既定ではツールが1つも公開されません。 使う機能を環境変数で明示的に有効にしてください。

対象範囲

項目

範囲

規制上の案内

日本国内での利用を対象とします

発信先

海外番号も許容しますが、既定では日本(国番号 81)のみ有効です

提供形態

利用者が自環境にデプロイします。1デプロイ = 1 Vonage アプリケーション

対応クライアント

MCP 仕様準拠。stdio と Streamable HTTP の2形態

IMPORTANT

規制に関する記載は法的助言ではありません。 このプロジェクトが案内するのは日本国内での利用を対象とした事項に限られます。他地域で利用する場合は、その地域の規制・キャリア仕様・Vonage の利用規約をご自身で確認する責任を負います。 日本国内での利用についても、日本向け SMS の利用条件に挙げた事項はサーバー側で判定できないため、遵守は利用者の責任です。

WARNING

単一インスタンスで動かしてください。 レートリミット・配信ステータス・Webhook のリプレイ検出はすべてプロセス内のメモリに保持されます。複数インスタンスで動かすと、

  • レートリミットが実効的にインスタンス数倍に緩みますRATE_LIMIT_PER_HOUR=5 を3インスタンスで動かせば毎時15件)

  • Webhook がインスタンス A に届いて get_sms_status が B で処理されると、配信ステータスを取得できません

  • Webhook のリプレイ検出がインスタンスをまたげません

Cloud Run なら --max-instances=1 を指定してください。外部ストア対応は現時点のスコープ外です。

Related MCP server: twilio-mcp

対応プラットフォーム

本サーバーは MCP 仕様に準拠した stdioStreamable HTTP の2形態を実装しており、プラットフォーム固有の分岐は持ちません。以下は各基盤の公開ドキュメントに基づく対応状況です。

凡例: ✅ 実機で確認済み / 📄 ドキュメント上は対応(未検証)/ ⚠️ 制約あり

プラットフォーム

接続方法

送れる認証

ツール実行前の承認

状態

Claude Desktop(ローカル)

stdio / MCPB

不要

あり(読み取り系にも出ます)

Claude Code

stdio / HTTP

--header で Bearer

あり

📄

Streamable HTTP 全般(Cloud Run 等)

Streamable HTTP

Bearer / 上流 IAM

クライアント次第

Claude.ai / Desktop(リモート)

Streamable HTTP

OAuth、または静的ヘッダ(beta・組織管理者が設定)

あり

📄

Gemini Enterprise(コネクタ)

Streamable HTTP

OAuth 2.0 か「認証なし」のみOAuth モードで対応)

あり(既定で必ず出る)

📄

Gemini Enterprise(ADK で自作)

Streamable HTTP

任意ヘッダで Bearer

あり(ADK の require_confirmation。Apps に承認ウィンドウが出ます)

AWS Bedrock AgentCore Gateway

Streamable HTTP(SSE は使いません)

API キープロバイダで任意ヘッダに Bearer。IAM SigV4 は使えません(下記)

無し

Dify

Streamable HTTP(SSE は使いません)

任意ヘッダで Bearer

無し。 Workflow に Human Input ノードを置けば可

ChatGPT(カスタムプラグイン / コネクタ)

Streamable HTTP

OAuth 2.0 か「認証なし」のみOAuth モードで対応)

クライアント次第

📄

n8n(MCP Client Tool)

HTTP Streamable / stdio

Bearer / 任意ヘッダ / OAuth2

AI Agent ノードで有効化すれば可

📄

NOTE

「📄」はまだ実機で確認していないという意味です。各基盤のドキュメント上は接続できるはずですが、動作報告をいただけると助かります。

Claude Desktop は 2026-08-24 に v1.34493 で確認しました。 MCPB のインストール、capability トグル、ALLOWED_NUMBERS によるブロック、SMS の実配信と音声通話の実発信、通話ステータスの取得、実行前の承認プロンプトまで動作しています。 ただし readOnlyHint は尊重されず、get_sms_statusget_call_status のような読み取り専用ツールでも承認プロンプトが出ます(安全側の挙動なので実害はありません)。

Streamable HTTP(Cloud Run)も同日に確認しました。 Bearer 認証(未認証は 401)、ALLOWED_NUMBERS によるブロック、SMS と音声通話の実行に加えて、 get_sms_statusdelivered を返すこと(Status Webhook 経由)と、get_call_status が通話時間・料金を返すことを確認しています。 配信ステータスは stdio では取得できません。 受け取るには HTTP で待ち受け、Vonage に Status URL を登録する必要があります。

Dify Cloud(Sandbox プラン)は 2026-08-31 に確認しました。 カスタムヘッダでの Bearer 認証、tools/list の取り込み、dry_runSMS の実送信と音声の実発信get_sms_statusdeliveredget_call_status の料金・通話時間まで動作しています。detail: ok / sip_code: 200 も返っており、Event Webhook 経由の失敗理由の伝達もこの経路で動いています。 手順は docs/dify.md。 ただし Dify には実行前の承認 UI がありません。 Agent アプリはエージェントの判断だけでツールを実行し、destructiveHint は無視されます。Agent アプリで使うなら ALLOWED_NUMBERS が唯一の防御です(Workflow なら Human Input ノードを置けます)。

AWS Bedrock AgentCore Gateway は 2026-08-31 に ap-northeast-1 で確認しました。 API キープロバイダによる Bearer 認証、ターゲット作成時の自動同期、**Strands + Bedrock のエージェントが自分でツールを選んでの SMS 実送信(delivered)と音声実発信(completed / sip_code: 200 / detail: ok)**まで動作しています。手順は docs/agentcore.mdIAM SigV4 の outbound はこのサーバーでは使えません。 Gateway は署名するだけで、ターゲット側が SigV4 を検証できる必要があり、対応するのは API Gateway / Lambda Function URLs / AgentCore Runtime です。Cloud Run は含まれません。 Gateway は API であって UI ではないため、承認は一切ありません。 ここでも ALLOWED_NUMBERS が唯一の防御です。また ツール名に <ターゲット名>___ が前置されるので、ツール名を名指しする指示文はそのままでは使えません。

ツール実行前の承認について

send_sms / make_voice_call には、MCP のツール注釈で destructiveHint: true を付けています。これを解釈する基盤(Gemini Enterprise など)では、実行前に確認が表示されます。get_sms_status / get_call_statusreadOnlyHint: true なので確認は省かれます。

WARNING

注釈は仕様上ヒントであり、強制ではありません。 無視する基盤もあり、「常に許可」を選べる基盤もあります。実測でも、Claude Desktop は readOnlyHint を尊重せず、読み取り専用ツールにも承認プロンプトを出しました。 上の表で承認が「無い」基盤を使う場合は、ALLOWED_NUMBERSRATE_LIMIT_PER_HOUR を必ず設定してください。承認UIもプロンプトも、実効的な防御にはなりません。

インストール方法

方法1: MCPB Bundle(推奨 - ワンクリックインストール)

Claude Desktopで簡単にインストールできます:

  1. MCPBファイルのダウンロード

  2. Claude Desktopで開く

    • .mcpbファイルをダブルクリック、またはClaude Desktopにドラッグ&ドロップ

  3. 環境変数の設定

    • Claude Desktopのインストールダイアログで以下を入力:

      • VONAGE_APPLICATION_ID: Vonage Application ID

      • VONAGE_PRIVATE_KEY_PATH: 秘密鍵ファイルのパス(例: /Users/your-name/vonage/private.key

      • VONAGE_VOICE_FROM: 音声通話用の電話番号(E.164形式、例: 81345438093

  4. インストール完了

    • Claude Desktopを再起動すると、Vonage MCPサーバーが利用可能になります

方法2: 手動セットアップ

セットアップ

依存関係のインストール

npm install

Vonage設定

  1. Vonageアカウントの作成

  2. 秘密鍵の準備

    • Vonage Developer Portalで秘密鍵(private.key)をダウンロード

    • プロジェクトルートに private.key として保存

  3. 環境変数の設定

    cp .env.example .env

    .env ファイルを編集して以下を設定:

    VONAGE_APPLICATION_ID=your_application_id_here
    VONAGE_PRIVATE_KEY_PATH=./private.key
    VONAGE_VOICE_FROM=14155550100  # Voice通話用のFROM番号

機能の有効化(capability トグル)

このサーバーは、既定ではツールを1つも公開しません。 使う機能だけを環境変数で明示的に有効にしてください。電話とSMSは実際に課金が発生し、相手にも迷惑がかかりうる操作なので、「気づかないうちに使える状態になっていた」を避けるための設計です。

環境変数

有効になるツール

既定

ENABLE_SMS

send_sms / get_sms_status

OFF

ENABLE_VOICE

make_voice_call / get_call_status

OFF

# SMSの単発送信だけを使う場合
ENABLE_SMS=true

無効なツールは tools/list の結果に含まれません。エージェントが存在しないツールを呼ぼうとして迷走せず、使わないツールの定義がコンテキストを消費することもありません。

WARNING

v1.3.0 の破壊的変更: generate_jwt ツールを削除しました。 Vonage API を直接叩ける署名済みクレデンシャルを呼び出し側に渡すツールで、受け取った相手にはこのサーバーのガードレール(宛先制限・レートリミット・capability トグル)が一切効きません。既定 OFF にしても、一度有効化した後にプロンプトインジェクションで長寿命のトークンを生成させられる余地が残ります。 AIエージェントから Vonage を使いやすくするという本サーバーの目的に対して汎用の JWT 発行は主要な用途ではないため、迂回路を残さない判断をしました。JWT が必要な場合は Vonage 公式のサーバーSDK を直接お使いください。

IMPORTANT

有効な値はtrue / false のみで、大文字小文字を区別します。ENABLE_SMS=TrueENABLE_SMS=1 は起動エラーになります。無効にしたつもりの false が truthy と判定されて機能が公開される事故を防ぐため、曖昧な値は推測せずに落とす方針です。

NOTE

capability を有効にした場合、VONAGE_APPLICATION_ID は必須になります。ENABLE_VOICE=true の場合はさらに VONAGE_VOICE_FROM が必要で、いずれも未設定なら起動時にエラーになります(実行して初めて失敗するより、起動時に気づけるほうが安全なため)。

安全機能(Guardrails)の環境変数

AIエージェント(Gemini Enterprise / Claude 等)から利用する際の、意図しない課金・スパム送信を防ぐための設定です。すべて任意で、未設定でも動作します。

環境変数

デフォルト

説明

ALLOWED_COUNTRY_CODES

81(日本のみ)

送信・架電を許可する国番号(カンマ区切り、+ の有無は問わない)。海外宛を使う場合は明示的に追加する。* を指定すると制限を外す(非推奨)。実在しない国番号を書くと起動エラーになる。

ALLOW_PREMIUM_NUMBERS

false

true にすると、0990(情報料代理徴収)・0570(ナビダイヤル)・0180(テレドーム)への送信・架電を許可する。

ALLOWED_NUMBERS

(未設定=制限なし)

送信・架電を許可する宛先番号のホワイトリスト(カンマ区切り)。設定すると、これ以外の番号へのリクエストはエラーになる。表記ゆれ(090-1234-5678 等)は正規化して比較される。

RATE_LIMIT_PER_HOUR

5

1時間あたりの送信・架電件数の上限(010000 の整数)。send_sms / make_voice_call合計に適用される。0 は「すべて拒否」(緊急停止)。dry_run: true の呼び出しは消費しない。

SMS_RATE_LIMIT_PER_HOUR

(未設定=RATE_LIMIT_PER_HOUR に委ねる)

SMS だけをさらに絞りたい場合の上限。

SMS_SEGMENT_LIMIT_PER_HOUR

(未設定=制限なし)

1時間あたりのセグメント数の上限。課金と直結する唯一の設定

SMS_MAX_SEGMENTS

3

1通のSMSに許すセグメント数(110)。

VOICE_RATE_LIMIT_PER_HOUR

(未設定=RATE_LIMIT_PER_HOUR に委ねる)

架電だけをさらに絞りたい場合の上限。

DISABLE_RATE_LIMIT

false

true にするとレートリミットを完全に無効化する。危険な設定であり、起動のたびに警告が出る。本番環境では使わないこと。

VONAGE_API_SIGNATURE_SECRET

(未設定)

Status Webhook の署名検証に使う Vonage の Signature Secret。推奨。Vonage Dashboard の Settings → API settings で取得できる。

VOICE_INBOUND_MESSAGE

(案内文)

音声の着信時に読み上げる文面。このサーバーは着信を処理しないため、既定では「お受けしておりません」という案内を読み上げて切る。1000文字以内(発信の読み上げと同じ上限)。着信は誰でも掛けられるため、長い案内文はそのまま通話時間と音声合成の課金になる。超過すると起動時にエラーで止まる。

VONAGE_WEBHOOK_SECRET

(未設定)

署名検証が使えない環境向けの代替。設定すると x-webhook-secret ヘッダーの一致を要求する。VONAGE_API_SIGNATURE_SECRET が設定されている場合は使われない。Vonage は任意ヘッダーを送れないため、Vonage から直接呼ばれる Webhook には使えない(手前のゲートウェイがヘッダーを付与する構成でのみ有効)。

WEBHOOK_MAX_AGE_SECONDS

300

署名付き Webhook の iat / exp に許す時刻のずれ(秒、13600)。短いほどリプレイ可能な時間窓が縮む。

# 検証中は自分の番号だけに送信を許可する例
ALLOWED_NUMBERS=+819012345678,+819087654321
RATE_LIMIT_PER_HOUR=3
VONAGE_API_SIGNATURE_SECRET=your_signature_secret_here

日本向け SMS の利用条件

NOTE

ここに書いているのは法的助言ではありません。Vonage の利用規約およびキャリアの仕様として公開されている事項を、利用者が確認すべき論点として整理したものです。最新の条件は Vonage のサポート記事と契約書をご確認ください。 このプロジェクトの規制案内は日本国内での利用を対象としています。他地域の利用者はご自身で確認する責任を負います。

守るべき利用条件

項目

内容

マーケティング目的の送信

受信者からのオプトイン同意が必須

P2P(個人間)トラフィック

禁止

禁止コンテンツ

政治 / 宗教 / 未承諾プロモーション / ギャンブル

これらはサーバー側では判定できません。 本文の内容や同意取得の有無はサーバーからは分からないため、利用者の責任で遵守してください。

配信の不確実性 — もっとも注意が必要な点

WARNING

日本のネットワークでは、URL を含むメッセージがフィッシング SMS 対策として配信されないことがあります。拒否基準は非公開です。 問題は、API が成功を返すのに実際には届かないことです。AIエージェントは失敗を検知できないため、放っておくと「送信できました」とユーザーに報告してしまいます。

このサーバーはこう扱います。

  • ブロックはしません。 正当な用途がありますし、拒否基準が非公開である以上こちらで判定しきれません

  • 日本宛で URL を含む本文の場合、dry_run と成功レスポンスに delivery_warning を添えます

  • send_sms の description に「成功レスポンスは配信保証ではない」と明記しています

重要な連絡に SMS を使う場合は、get_sms_status で配信結果を確認し、別の手段も併用してください。

文字数

Unicode 連結メッセージの上限は Docomo / KDDI / Softbank が 670文字、Rakuten が 660文字です。このサーバーは安全側の 660文字を絶対上限とし、実際の制限はセグメント数で掛けています。

content_id / entity_id について

Vonage Messages API には「特定国の規制要件を満たすためのパラメータ」として content_id / entity_id がありますが、これらはインドの DLT(Distributed Ledger Technology)登録制度のためのものですentity_id が DLT に登録した Principal Entity ID、content_id が承認済みテンプレート ID にあたります。

日本宛の送信では不要なため、このサーバーでは使用していません。

送信者ID(sender ID)のルール

SMS の from には Vonage 公式ルールが適用されます。**英数字1〜11文字(A-Z a-z 0-9)**で、先頭文字と最小長の制限はありません。2FAAB も有効です。

日本宛には、これに加えて日本の携帯キャリア固有の制限がかかります。

種別

日本宛

挙動

英数字(例: VonageMCP

✅ 使用可

そのまま表示される

電話番号(例: +819012345678

❌ 使用不可

拒否します

汎用語(INFO / SMS / NOTICE など)

❌ 使用不可

拒否します

IMPORTANT

日本宛で電話番号を送信元に指定しても、Vonage 側で別の送信者IDに上書きされます。 このサーバーは、これを「送れる」と応答せずに拒否します。通してしまうと、dry_run が「この番号から送ります」と答えてユーザーが承認したのに、実際にはまったく別の送信者IDで届くからです。承認した内容と届く内容が食い違うのが、いちばん避けたい失敗の形です。

日本以外が宛先の場合、これら2つの制限は適用されません(自社の発信元電話番号を送信元に使えます)。

宛先のガードレール

宛先には、緩められるものと緩められないものの2種類の制限がかかります。

対象

挙動

緩められるか

緊急通報番号(110 / 119 / 118

常にブロック

不可

高額課金番号(0990 / 0570 / 0180

既定でブロック

ALLOW_PREMIUM_NUMBERS=true

国番号

既定で日本(81)のみ

ALLOWED_COUNTRY_CODES

個別の番号

未設定なら制限なし

ALLOWED_NUMBERS

これらは AND で効きます。ALLOWED_NUMBERS に載せた番号でも、国番号が許可されていなければブロックされます。判定はすべて dry_run: true の時点で行われるので、「dry_run は通ったのに本番で弾かれた」は起きません。

WARNING

ALLOWED_COUNTRY_CODES は IRSF(国際収益分配詐欺)に対する主防御にはなりません。 国番号と国は一対一ではありません。+1 は米国・カナダに加えてカリブ海の多数の国が共有しているため、「米国宛だけ許可」というポリシーはこの仕組みでは表現できません。米国宛のつもりで 1 を追加すると、同じ +1 配下の高リスク地域も同時に開きます。 実効的な防御は以下の併用です:

  • ALLOWED_NUMBERS による宛先の個別指定(もっとも確実)

  • Vonage アカウント側の地域制限・利用額上限・アラート(サーバーを迂回されても効く唯一の層)

  • RATE_LIMIT_PER_HOUR による被害額の上限

NOTE

短縮番号(110 や海外の 911 / 112 など)は、E.164 の桁数要件を満たさないため一律で拒否されます。日本の緊急通報番号については、桁数検証とは独立した明示的なブロックも入れています(桁数の扱いが将来変わっても効き続けるようにするため)。

レートリミットの数え方

「ツール呼び出し回数」ではなく「送信件数」で消費されます。 どのツールを使ったかではなく、何件送ったかで枠が減ります。

ただしSMS の課金はセグメント単位です(SMS の課金単位)。件数の枠は「操作の回数」を抑えるもので、費用そのものを抑えたい場合は segments の枠を使ってください。

枠はツールごとではなく、次の2層で管理されます。

バケット

消費単位

対象

環境変数

global

件数

SMS・架電のすべて

RATE_LIMIT_PER_HOUR

sms

件数

send_sms

SMS_RATE_LIMIT_PER_HOUR

voice

件数

make_voice_call

VOICE_RATE_LIMIT_PER_HOUR

segments

セグメント数

SMS のみ

SMS_SEGMENT_LIMIT_PER_HOUR

1回の送信は該当するバケットを同時に消費します。どれか1つでも足りなければどれも消費せずエラーになるので、「送っていないのに枠だけ減る」ことはありません。エラーレスポンスの exceeded_bucket に、どのバケットで不足したかが入ります。

SMS の課金単位 — セグメント

SMS の課金は通数ではなくセグメント単位です。 1セグメントに入る文字数はエンコーディングで変わります。

エンコーディング

1通の場合

連結時(1セグメントあたり)

GSM-7(英数字のみ)

160文字

153文字

UCS-2(日本語などを含む)

70文字

67文字

非ASCII文字が1文字でも混ざると、本文全体が UCS-2 になります。 英数字159文字+日本語1文字で、1セグメントから3セグメントに跳ねます。

IMPORTANT

RATE_LIMIT_PER_HOUR=5 は「1時間に5通まで」であって「5通分の課金まで」ではありません。 日本語で160文字のSMSは3セグメント=3通分の課金になるため、5通送ると実際の課金は約15通分です。 費用そのものを抑えたい場合は SMS_SEGMENT_LIMIT_PER_HOUR を設定してください。これがセグメント数を直接数える唯一の設定です。 設定しない場合の最悪ケースは RATE_LIMIT_PER_HOUR × SMS_MAX_SEGMENTS セグメント(既定なら 5 × 3 = 15)です。

本文の上限はセグメント数で指定しますSMS_MAX_SEGMENTS、既定 3)。文字数で縛っても課金と対応しないためです。既定の3セグメントは、日本語なら約200文字、英数字なら約450文字に相当します。

dry_run のレスポンスに encodingsegments が含まれるので、送信前にユーザーへ提示してください

IMPORTANT

RATE_LIMIT_PER_HOUR=5 は「1時間に合計5件まで」を意味します。 ツールごとに別枠ではありません。送信手段を変えて上限を超えることはできません。

WARNING

v1.3.0 の破壊的変更: SMS 本文の上限が「160文字」から「3セグメント」に変わりました。 英数字だけなら約450文字まで送れるようになり(従来より緩和)、日本語では約200文字までになります(従来の160文字より緩和)。ただし絵文字や記号を多用すると従来より厳しくなる場合があります。 文字数で縛っても課金と対応しないためです。同じ160文字でも、英数字なら1通分、日本語なら3通分の課金でした。

WARNING

v1.3.0 の破壊的変更: RATE_LIMIT_PER_HOUR=0 の意味が反転しました。 v1.2.1 以前は 0 が「無制限」でしたが、v1.3.0 以降は「すべて拒否」になります。緊急停止のつもりで 0 を設定した管理者が、逆に無制限にしてしまう事故を防ぐためです。 無制限にしたい場合は DISABLE_RATE_LIMIT=true を明示的に設定してください。

IMPORTANT

環境変数は起動時に厳格に検証されます。解釈できない値があると、サーバーは警告を出して動き続けるのではなく、エラーメッセージを表示して起動に失敗します(fail-fast)。

  • 真偽値(ENABLE_* / DISABLE_RATE_LIMIT)に指定できるのは true / false のみです。大文字小文字を区別し、1 / yes / on / True はすべてエラーになります。False のような値を truthy と誤判定して、無効にしたつもりの設定が有効になる事故を防ぐためです

  • 数値(RATE_LIMIT_PER_HOUR / SMS_MAX_SEGMENTS)は10進整数のみです。小数・指数表記・負数・範囲外はエラーになります

  • 問題はまとめて報告されます。1つ直すたびに再起動する必要はありません

IMPORTANT

ALLOWED_NUMBERS を設定しているのに有効な電話番号が1件も解釈できない場合(例: ALLOWED_NUMBERS=,)、「制限なし」ではなくすべて拒否として扱います。設定ミスを安全側に倒すためです。制限が不要な場合は環境変数自体を削除してください。

IMPORTANT

VONAGE_API_SIGNATURE_SECRETVONAGE_WEBHOOK_SECRETどちらも未設定の場合、Status Webhook エンドポイントは 503 を返して無効化されます。未認証で受け付けると、誰でも任意の message_id の配信ステータスを偽装できてしまうためです。

IMPORTANT

署名付き Webhook では、署名の一致だけでなく payload_hash / iat / jti をすべて検証し、いずれかが欠けていれば 401 で拒否します。 署名が正しいことは「Vonage が一度発行した」ことしか意味しません。claim が無ければ検証をスキップする実装だと、攻撃者は claim を外した JWT を作るだけで検証を無効化できます。有効な JWT が一度でもログやプロキシから漏れた場合に、無期限に、任意のボディと組み合わせて再利用されるのを防ぐための措置です。

  • payload_hash: このボディに対して発行された署名か

  • iat / exp: WEBHOOK_MAX_AGE_SECONDS 以内に発行されたものか(未来方向のずれも拒否)

  • jti: 同じ JWT の使い回しでないか(受理済みの jti は許容時間内は記憶される)

WARNING

VONAGE_API_SIGNATURE_SECRET を設定した場合、共有シークレット認証にはフォールバックしません。 両方を設定していても、署名検証に失敗したリクエストは x-webhook-secret が正しくても 401 になります。フォールバックすると、攻撃者は Authorization ヘッダーを外すか壊すだけで弱いほうの方式を選べてしまうためです(ダウングレード攻撃)。

NOTE

レートリミットはオンメモリ管理のため、プロセスを再起動するとカウントはリセットされます。

開発用依存関係のインストール

npm install --save-dev @types/node typescript ts-node

開発

開発サーバーの起動

npm run dev:start

TypeScriptのコンパイル

npm run build

コンパイルされたコードの実行

# 環境変数ファイル(.env)を使用して実行(推奨・Node.js v22以降)
npm start

# 環境変数ファイルを使用せずに実行(従来方式)
npm run start:legacy

ファイル監視モード(コンパイル)

npm run dev

ビルドファイルのクリーンアップ

npm run clean

テストの実行

npm test

テストの監視モード

npm run test:watch

カバレッジ付きテスト

npm run test:coverage

Claude Desktopでの利用

このMCPサーバーをClaude Desktopで利用するための設定方法を説明します。

方法1: MCPB Bundle(推奨 - ワンクリックインストール)

.mcpbファイルを使用すると、Claude Desktopに簡単にインストールできます。

インストール手順

  1. MCPBファイルの作成

    mcpb CLI が必要です(未インストールなら一度だけ)。

    npm install -g @anthropic-ai/mcpb
    npm run build:mcpb

    これにより vonage-mcp-server.mcpb ファイルが作成されます。

  2. Claude Desktopで開く

    • 作成された .mcpb ファイルをダブルクリック

    • または Claude Desktop にドラッグ&ドロップ

  3. 環境変数の設定 Claude Desktop のインストールダイアログで以下を入力:

    • Vonage Application ID: Vonage Application ID

    • Private Key Path: 秘密鍵ファイルの絶対パス(例: /Users/your-name/vonage/private.key

    • Voice Call From Number: 音声通話用の電話番号(E.164形式、例: 81345438093

  4. インストール完了 Claude Desktop を再起動すると、Vonage MCP サーバーが利用可能になります。

MCPBファイルの配布

作成した .mcpb ファイルは他のユーザーと共有できます:

  • GitHub Releases で配布

  • 直接ファイルを共有

方法2: 手動セットアップ

1. サーバーのビルドと起動

# プロジェクトをビルド
npm run build

# サーバーを起動(Node.js v22以降、推奨)
npm start

# または従来方式で起動(環境変数ファイルを使用しない場合)
npm run start:legacy

2. Claude Desktopの設定

Claude Desktopの設定ファイル claude_desktop_config.json に以下の設定を追加します:

{
  "mcpServers": {
    "vonage-mcp-server": {
      "command": "node",
      "args": ["--env-file=.env", "dist/index.js"],
      "cwd": "/Users/your-username/path/to/vonage-mcp-server"
    }
  }
}

または環境変数を直接指定する方法もあります:

{
  "mcpServers": {
    "vonage-mcp-server": {
      "command": "node",
      "args": ["/Users/your-username/path/to/vonage-mcp-server/dist/index.js"],
      "env": {
        "VONAGE_APPLICATION_ID": "your-application-id",
        "VONAGE_PRIVATE_KEY_PATH": "/Users/your-username/path/to/vonage-mcp-server/private.key"
      }
    }
  }
}

設定ファイルの場所

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

設定手順

  1. 上記のパスにある claude_desktop_config.json を開く

  2. mcpServers セクションに上記の設定を追加

  3. ファイルを保存

  4. Claude Desktopを再起動

3. 利用可能な機能

設定完了後、Claude Desktopで以下の機能が利用できます:

ツール

すべてのツールは軽量なJSONを返します(Vonage APIの生レスポンスは返しません)。詳細は ツールのレスポンス形式 を参照してください。

  • send_sms: 単発SMS送信ツール

    • 入力:

      • to (必須): 送信先の電話番号(E.164形式 +819012345678 または日本の国内形式 09012345678

      • message (必須): 送信するメッセージ(最大3セグメント — 日本語なら約200文字、英数字なら約450文字。SMS の課金単位を参照)

      • from (オプション): 送信元。英数字1〜11文字(A-Z a-z 0-9)。省略時は'VonageMCP'。dry_run の時点で検証される。送信者ID(sender ID)のルールを参照

      • dry_run (オプション): true で送信せず検証のみ(デフォルト: false

    • 機能:

      • 日本の電話番号(0から始まる)は自動的にE.164形式に変換

      • {"status":"success","message_id":"...","to":"+81..."} を返却

  • make_voice_call: 音声通話ツール

    • 入力:

      • to (必須): 発信先電話番号(E.164形式または0ABJ形式)

      • message (必須): 読み上げるメッセージ(最大1000文字)

      • voice (オプション): 女性 または 男性(デフォルト: 女性)

      • dry_run (オプション): true で発信せず検証のみ

    • 機能:

      • 指定番号に発信してメッセージを音声で読み上げ

      • 日本語音声対応(女性・男性)

      • NCCO(Nexmo Call Control Object)を使用

      • 通話時間の自動見積もり。見積もりから通話の強制切断時間(length_timer)を決めるため、dry_run で提示した時間を大きく超えて課金されることはない

      • dry_runestimated_duration_seconds(見積もり)と max_duration_seconds(実際に適用される上限)の両方を返す

  • get_call_status: 通話ステータス取得ツール

    • 入力:

      • call_id (必須): 取得する通話のCall ID(UUID形式)

    • 機能:

      • Vonage Voice APIから通話のステータス情報を取得

      • call_status(通話ステータス)、start_timepricerateduration_seconds を返却

      • 環境変数から自動的にApplication IDとPrivate Keyを読み込み

  • get_sms_status: SMS配信ステータス取得ツール

    • 入力:

      • message_id (必須): send_sms が返した message_id

    • 機能:

      • delivery_statussubmitted / delivered / failed 等)を返却

      • Vonage Messages APIは配信ステータスを同期取得できないため、HTTPサーバー版の Status Webhook(POST /webhooks/message-status)で受信した結果を参照する

      • Webhook未設定時・stdio版では submitted のまま(note フィールドで明示される)

      • 記録はオンメモリで24時間保持(プロセス再起動でクリア)

ツールのレスポンス形式

status

意味

success

実行成功

{"status":"success","message_id":"abc","to":"+819012345678"}

dry_run_success

検証のみ成功(API呼び出しなし)

{"status":"dry_run_success","message":"Ready to send","to":"+819012345678","characters":12}

error

失敗

{"status":"error","reason":"無効な電話番号形式です: 123","suggestion":"番号のフォーマットを確認してください。..."}

エラー時は必ず reason(原因)と suggestion(AIが次に取るべき行動)が含まれます。再試行が無意味なケースでは suggestion にその旨が明記されるため、AIエージェントの無限リトライを防げます。

4. 使用例

Claude Desktopで以下のような質問ができます:

単発SMS送信

「090XXXXYYYYに「これはVonage MCPサーバーを使って送信しています。」とSMSを送ってください」
→ send_smsツールを使用してSMS送信

音声通話

「090XXXXYYYYに女性の声で『会議は明日の10時からです』と電話をかけて」
→ make_voice_callツールを使用して発信・音声読み上げ

「080XXXXYYYYに男性の声で『システム障害が発生しました。至急対応をお願いします』と電話で伝えて」
→ make_voice_callツールを使用して緊急連絡

通話ステータス取得

「Call ID ca6b7710-3423-4c8d-b630-7b981ec4b2c2 の通話ステータスを取得してください」
→ get_call_statusツールを使用して通話情報を取得

「先ほどの通話の料金と時間を教えてください」
→ get_call_statusツールで通話詳細を確認

Voice通話機能

機能概要

Voice APIを使用して自動音声通話を発信し、指定されたメッセージを日本語で読み上げます。

主な特徴

  • 自動発信: 指定番号への自動発信

  • 日本語音声: 女性・男性音声による自然な読み上げ

  • NCCO制御: Nexmo Call Control Objectによる通話フロー制御

  • 通話時間見積: メッセージ長から自動的に通話時間を算出し、通話の強制切断時間に連動させる

IMPORTANT

通話時間には絶対上限(300秒)があります。 v1.2.1 以前は Vonage へ length_timer: 7200(2時間)を送っていました。dry_run が「約N秒」と提示してユーザーが承認しても、NCCO の挙動や機械検出の結果によっては最大2時間まで課金され得る状態でした。音声は分課金なので、SMS と違って金額の跳ね方が大きい点が問題です。 現在は「見積もり + 30秒の余裕」を上限として送り、どんな場合も 300 秒を超えません。dry_runmax_duration_seconds が実際に適用される値です。

音声オプション

音声タイプ

性別

言語

特徴

女性

女性

日本語

自然で聞き取りやすい(デフォルト)

男性

男性

日本語

落ち着いた男性音声

使用例

// 会議リマインダー
make_voice_call({
  to: "090-1234-5678",
  message: "明日の会議は10時から会議室Aで行います。資料をご準備ください。",
  voice: "女性"
})

// 緊急連絡
make_voice_call({
  to: "080-9876-5432",
  message: "システム障害が発生しました。至急対応をお願いします。",
  voice: "男性"
})

通話ステータス取得機能

機能概要

Vonage Voice APIを使用して、過去の通話のステータス情報を取得します。通話の詳細(ステータス、料金、レート、通話時間)を確認できます。

主な特徴

  • 詳細情報取得: 通話のステータス、料金、レート、通話時間を一度に取得

  • 自動設定読み込み: 環境変数から自動的にApplication IDとPrivate Keyを取得

  • エラーハンドリング: 存在しないCall IDに対する適切なエラーメッセージ

パラメータ

パラメータ

説明

call_id

string

取得する通話のCall ID(UUID形式)。必須

返却される情報

  • status: 通話のステータス(completed, answered, busy, failed など)

  • start_time: 通話開始時刻(ISO 8601形式)

  • price: 通話料金(数値形式)

  • rate: 通話レート(1分あたりの料金)

  • duration: 通話時間(秒単位)

使用例

// Call IDを指定して通話ステータスを取得
get_call_status({
  call_id: "ca6b7710-3423-4c8d-b630-7b981ec4b2c2"
})

// 結果例:
// ステータス: completed
// 開始時刻: 2025-12-10T03:53:19.000Z
// 料金: 0.06287850
// レート: 0.13973000
// 通話時間: 27秒

5. トラブルシューティング

サーバーが起動しない場合

  • npm run build が正常に完了しているか確認

  • npm start でエラーが出ないか確認

  • Node.jsバージョンが20.6.0以降であることを確認(node -v

Claudeデスクトップでのエラー

  • JSON解析エラー「Unexpected token 'd', "[dotenv@17."... is not valid JSON」が表示される場合:

    • claude_desktop_config.jsonのargsに--env-file=.envが含まれていることを確認

    • サーバーコードがdotenvを使用していないことを確認(最新のコードではdotenvは使用していません)

    • MCPサーバーを再起動

Claude Desktopで認識されない場合

  • claude_desktop_config.json の設定が正しいか確認

  • 作業ディレクトリ(cwd)のパスが正しいか確認

  • Claude Desktopを再起動

機能が利用できない場合

  • サーバーのログを確認(Claude Desktopの設定画面で確認可能)

  • サーバーを再起動

Voice通話機能のトラブルシューティング

  • Voice通話が発信されない場合:

    • VONAGE_VOICE_FROM環境変数が正しく設定されているか確認

    • VonageアプリケーションでVoice機能が有効になっているか確認

    • FROM番号がVonageアカウントに登録されているか確認

  • 通話は繋がるが音声が再生されない場合:

    • NCCOパラメータの音声設定を確認

    • 音声オプション(女性/男性)が正しく指定されているか確認

プロジェクト構造

vonage-mcp-server/
├── src/                    # TypeScriptソースコード
│   ├── index.ts           # stdio版エントリーポイント
│   ├── http-server.ts     # HTTP版エントリーポイント・Webhook受信
│   ├── tools.ts           # MCPツール定義の共通レジストリ(stdio/HTTP共用)
│   ├── guardrails.ts      # 電話番号検証・ホワイトリスト・レートリミット
│   ├── toolResponse.ts    # 軽量JSONレスポンスの整形
│   ├── messageStatusStore.ts # SMS配信ステータスのオンメモリ保持
│   ├── webhookAuth.ts     # Vonage署名付きWebhookの検証
│   ├── vonage.ts          # Vonage SMS送信機能
│   ├── voiceCall.ts       # Voice通話機能・NCCO生成
│   └── callStatus.ts      # 通話ステータス取得機能
├── tests/                  # テストファイル
│   ├── index.test.ts      # メイン機能のテスト
│   ├── utils.test.ts      # ユーティリティのテスト
│   ├── callStatus.test.ts # 通話ステータス取得のテスト
│   ├── tools.test.ts      # ツールレジストリ・ガードレール統合のテスト
│   ├── guardrails.test.ts # ホワイトリスト・レートリミットのテスト
│   ├── messageStatusStore.test.ts # 配信ステータス保持のテスト
│   ├── http-server.test.ts # HTTPラッパーのテスト
│   └── integration.test.ts # 統合テスト
├── docs/
│   ├── deployment.md      # デプロイ手順
│   ├── setup-guide.md     # セットアップガイド (PDF の元原稿)
│   ├── gemini-enterprise-adk.md # Gemini Enterprise (ADK 経路) の手順
│   ├── dify.md            # Dify から使う手順
│   ├── agentcore.md       # AWS Bedrock AgentCore Gateway の手順
│   └── gemini_system_instruction.md # Gemini Enterprise向けSystem Instruction

### HTTPラッパー (Dify / 外部アプリ用)

HTTPラッパーを使用してサーバーを実行することで、外部アプリケーション(Difyなど)からHTTP POSTリクエスト経由でMCPツールを呼び出すことができます。

```bash
npm run start:http

これにより、ポート3000(デフォルト)でHTTPサーバーが起動します。

認証

MCP エンドポイント (/mcp) は Bearer トークンで認証します。

# 32バイトのランダムな値を生成して設定する
MCP_AUTH_TOKEN=$(openssl rand -hex 32)
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $MCP_AUTH_TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

環境変数

既定

説明

MCP_AUTH_TOKEN

(未設定)

/mcp の Bearer トークン。16文字以上。短い値は起動エラーになる。

TRUST_UPSTREAM_AUTH

false

true にすると、このサーバー自身は認証せず上流(Cloud Run IAM / API Gateway など)に任せる。

BIND_HOST

認証があれば 0.0.0.0、無ければ 127.0.0.1

待ち受けアドレス。

PORT

3000

待ち受けポート。

ALLOWED_ORIGINS

(未設定=すべて拒否

CORS で許可するオリジン(カンマ区切り)。ブラウザから /mcp を呼ぶ場合のみ設定する。

ALLOWED_HOSTS

ループバック運用なら localhost / 127.0.0.1 / ::1

許可する Host ヘッダーのホスト名(カンマ区切り)。ポートは比較に含まれない。

OAuth 2.1(ChatGPT / Gemini Enterprise のコネクタなど)

MCP 仕様が定める認可の標準は OAuth 2.1 です。 上の MCP_AUTH_TOKEN による静的 Bearer は仕様には存在せず、基盤が任意のヘッダを設定させてくれる場合にだけ使えます(Claude Code / ADK / Dify / AgentCore / n8n はこれに当たります)。

一方、エンドユーザーが触る入り口 — ChatGPT のカスタムプラグイン、Gemini Enterprise のカスタムコネクタ — は「OAuth か認証なし」の2択しか出しません。 課金の発生するサーバーに「認証なし」は選べないため、この経路には OAuth でしか繋がりません。

このサーバーが担うのはリソースサーバー(RS)だけです。 認可サーバー(AS)は MCP 仕様でも明確にスコープ外とされており、外部の IdP(Auth0 / Okta / Entra ID / Keycloak など)を使ってください。

環境変数

必須

説明

OAUTH_ISSUER

認可サーバーの issuer。トークンの iss と照合します。https のみ(ループバックのみ http 可)。

OAUTH_RESOURCE

この MCP サーバーの正規 URI(RFC 8707 / RFC 9728 の resource)。例: https://example.com/mcp。フラグメントとクエリは指定できません。

OAUTH_JWKS_URI

アクセストークンの署名鍵の取得先。IdP の /.well-known/openid-configuration にある jwks_uri を使います。

OAUTH_AUDIENCE

トークンの aud に期待する値。既定は OAUTH_RESOURCE と同じ。IdP の API 識別子が URI と異なる場合だけ設定します。

OAUTH_SCOPES_SUPPORTED

保護リソースメタデータに載せる scope(カンマ区切り)。OAUTH_REQUIRED_SCOPE を設定するなら、それをここにも含めてください(含めないと起動エラーです。クライアントはこの一覧から認可要求を組み立てるため、案内どおりに取ったトークンが必ず 403 になります)。

OAUTH_REQUIRED_SCOPE

/mcp を呼ぶために必須の scope。設定すると、これを持たないトークンは 403 insufficient_scope になります。

OAUTH_REQUIRE_AT_JWT

既定は true RFC 9068 の typ: at+jwt を持つトークンだけを受け付けます。false にする場合は OAUTH_REQUIRED_SCOPE が必須になります(下記)。

OAUTH_ISSUER=https://your-tenant.example.com
OAUTH_RESOURCE=https://vonage-mcp.example.com/mcp
OAUTH_JWKS_URI=https://your-tenant.example.com/.well-known/jwks.json

設定すると次の2つが有効になります。

  • GET /.well-known/oauth-protected-resource(およびリソースのパスを付けた /.well-known/oauth-protected-resource/mcp)— RFC 9728 の保護リソースメタデータ。認証不要です。クライアントはトークンを持つ前にここを読み、どの認可サーバーへ行けばよいかを知ります

  • /mcp の 401 / 403 に WWW-Authenticate: Bearer resource_metadata="..." が付きます — これが discovery の起点です

IMPORTANT

必須の3つは揃っている必要があります。 1つでも欠けると起動エラーになります。部分的な設定を黙って無視すると、**「OAuth にしたつもりのサーバーが、実は静的トークンで動いていた」**という最も気づきにくい状態ができるためです。

IMPORTANT

有効期限(exp)を持たないアクセストークンは受け付けません。 無期限のトークンは、漏れた1本を失効させる手段がありません。 また OAUTH_ISSUER / OAUTH_RESOURCE書いたとおりの文字列として使います(末尾のスラッシュも含めて識別子の一部です)。IdP の設定と1文字違わないように書いてください。 OAUTH_REQUIRED_SCOPE / OAUTH_SCOPES_SUPPORTED に空白・二重引用符・バックスラッシュ・非 ASCII を含めると起動エラーになります(RFC 6749 の scope の文字集合)。OAUTH_ISSUER にクエリやフラグメントを含めることもできません(RFC 8414)。

IMPORTANT

http://localhost を使えるのは手元での確認だけです。 issuer / resource / JWKS のいずれかが http の場合、BIND_HOST を指定しなければ OAUTH_RESOURCE が指しているループバックアドレスで待ち受けますhttp://[::1]:3000/mcp を配るなら ::1。認証が構成済みでも 0.0.0.0 にはしません)。OAUTH_RESOURCE のポートと PORT、そしてアドレスの系統(IPv4 / IPv6)も揃っている必要があります(揃っていないと起動エラーです。配った URI に繋ぎに来たクライアントが接続できないため。localhost はどちらにも解決しうるので例外です)。平文でアクセストークンを受け取るサーバーを外部に出さないためです。この構成で BIND_HOST に外部アドレスを指定すると起動時にエラーで停止します。MCP_AUTH_TOKEN を併用していても同じです — 認証は「どちらか一方」で通るので、静的トークンがあってもアクセストークンは平文で流れます。

WARNING

ID トークンをアクセストークンとして受け取らないための設定が必須です。 ID トークンの aud はクライアント識別子です。同じ IdP が同じ鍵・同じ issuer で ID トークンも発行する構成で OAUTH_AUDIENCE をクライアント識別子に合わせてしまうと、iss / aud / exp では両者を区別できません。ログインできるだけの利用者が、API の委譲を受けないまま SMS を送れます。

そのため、「アクセストークンであることを積極的に示すもの」を必ず1つ要求します。 次のどちらかが必要で、両方欠けていると起動時にエラーで停止します。

方法

設定

使える条件

RFC 9068 の typ

OAUTH_REQUIRE_AT_JWT=true既定

IdP がアクセストークンに typ: at+jwt を付ける(Auth0 など)

API の scope

OAUTH_REQUIRE_AT_JWT=false + OAUTH_REQUIRED_SCOPE=...

IdP が typ を付けない(Keycloak / Entra ID など)。ID トークンは API の scope を運びません

at_hash / c_hash を持つトークンも拒否しますが、これは当てにできません — どちらも条件付きの claim で、認可コードフローの ID トークンには通常入っていないためです。「無いこと」は根拠になりません。

あわせて、OAUTH_AUDIENCE は API / リソースの識別子にしてください。 クライアント識別子を指定しないでください。

IMPORTANT

アクセストークンは audience を検証します。 同じ IdP が別のサービス向けに発行したトークンでは通りません(MCP 仕様の MUST)。IdP 側でこの MCP サーバーを1つの API / Resource として登録し、aud にその識別子が入るようにしてください。ここを省くと、「別のサービスを使う」つもりで同意しただけの利用者のトークンで SMS が送れてしまいます。

IMPORTANT

MCP_AUTH_TOKEN と併用すると、どちらの資格情報でも /mcp を通れます。 OAuth しか喋れない基盤と、任意ヘッダを送れる基盤を同じデプロイに繋ぐ構成は実際にあるため、片方を黙って無効化していません。この状態では起動のたびに警告が出ます。OAuth だけに絞るなら MCP_AUTH_TOKEN を削除してください。

WARNING

繋がるかどうかは IdP のクライアント登録方式で決まります。 ChatGPT や Claude のように事前の関係が無いクライアントは、Client ID Metadata Documents か動的クライアント登録(DCR)で client_id を得ます。IdP がどちらにも対応していない場合、その基盤からは接続できません。 基盤側で client_id / client_secret を手入力できるなら、事前登録でも構いません。

NOTE

/mcp の応答コードの意味づけ。 認証情報を送っていないリクエストには、error を付けない 401 のチャレンジを返します(未認証は失敗ではなく認可フローの1歩目です)。Bearer 以外の認証方式も同じ扱いです。Bearer と書いてあるのに中身が無い場合だけ 400 を返します。認可サーバーの署名鍵を取得できなかった場合は 503 で、invalid_token にはしません — トークンが無効とは限らず、401 を返すとクライアントは正当なトークンを捨てて取り直しに行きます。

NOTE

受け取ったアクセストークンは下流に流しません。 Vonage を呼ぶときに使うのは、アプリケーション ID と秘密鍵から組む別の JWT です。仕様が禁じる token passthrough には当たりません。

NOTE

OAuth 経路はまだ実機で確認していません。 仕様(MCP 2025-11-25 / Authorization)に沿って実装し、トークン検証・メタデータ配信・401 の挙動はテストで固めていますが、ChatGPT や Gemini Enterprise のコネクタから実際に繋いだ報告はまだありません。動作報告をいただけると助かります。

IMPORTANT

CORS は既定で閉じています。 Bearer トークン認証があっても、ブラウザ側がトークンを持つ構成(ブラウザ拡張や Web 版の MCP クライアント)では、CORS が開いていると悪意ある Web ページが /mcp を呼び、レスポンスまで読み取れます。ツールのレスポンスに含まれる宛先や配信状況も読み取られます。 MCP クライアントの多くはブラウザではないため、開ける必要があるのは例外的なケースだけです。必要な場合のみ ALLOWED_ORIGINS に列挙してください。

IMPORTANT

ループバック運用では Host ヘッダーを検証します(DNS rebinding 対策)。 攻撃者が自分のドメインを 127.0.0.1 に解決させると、ブラウザからは同一オリジンに見えるため CORS では防げません。このとき Host ヘッダーには攻撃者のドメインが入るので、そこで 403 を返します。 外部アドレスに bind する場合、正しい Host は運用者のドメインでありサーバー側からは分かりません。推測して塞ぐと正規のリクエストまで落ちるため、ALLOWED_HOSTS が明示されるまで検証しません。インターネットに公開する場合は ALLOWED_HOSTS の設定を推奨します。

WARNING

v1.3.0 の破壊的変更: X-API-KEY による認証を廃止しました。 v1.2.1 以前は X-API-KEY ヘッダを VONAGE_APPLICATION_ID と比較していました。Application ID は秘密情報ではありません — Vonage に送る JWT の claim に入る公開識別子です。これを認証に使うと、Application ID を知っている者は誰でも、そのデプロイの持ち主の課金で SMS 送信や架電ができてしまいます。MCP_AUTH_TOKEN に移行してください。

IMPORTANT

認証を設定しない場合、HTTPサーバーは 127.0.0.1 でのみ待ち受けます。 認証なしで BIND_HOST に外部アドレスを指定すると、起動時にエラーで停止します。 リクエストごとに接続元が localhost かを判定する方式は採っていません。Cloud Run やリバースプロキシの配下では、アプリから見た接続元が 127.0.0.1 になり、外部からのリクエストが全部「localhost」と判定されて無認証で通るためです。bind するアドレスならプロキシの有無に左右されません。

推奨構成: 認証は手前の層に置く

もっとも堅いのは、Cloud Run IAM や API Gateway をこのサーバーの手前に置く構成です。認証の実装をこのサーバーから切り離せるうえ、鍵のローテーションや監査ログもプラットフォーム側の仕組みに乗せられます。

その場合は TRUST_UPSTREAM_AUTH=true を設定してください(起動のたびに警告が出ます)。手前で認証していない環境でこれを有効にすると完全に無防備になります。

# Cloud Run IAM で認証する例(--allow-unauthenticated は付けない)
gcloud run deploy vonage-mcp-server \
  --set-env-vars TRUST_UPSTREAM_AUTH=true,ENABLE_SMS=true \
  --no-allow-unauthenticated

APIエンドポイント

経路

認証

GET /health

不要

POST /mcp

Bearer トークン(/mcp 配下は全 HTTP メソッドが対象)

POST /webhooks/*

Vonage の署名検証

ALL /mcp

MCP の Streamable HTTP エンドポイントです。POST (JSON-RPC) / GET (SSE) / DELETE (セッション終了) を MCP SDK の StreamableHTTPServerTransport が処理します。手書きの JSON-RPC 実装ではないので、仕様の追加に追従できます。

仕様どおり、クライアントは POST に Accept: application/json, text/event-stream を付ける必要があります(欠けていると 406 になります)。

curl -X POST http://localhost:3000/mcp \
  -H "Authorization: Bearer $MCP_AUTH_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
NOTE

セッションを持たないステートレス構成です。 リクエストごとにサーバーとトランスポートを生成し、Mcp-Session-Id を発行しません。 セッションを持つとその状態がプロセスのメモリに載るため、Cloud Run のように複数レプリカへ分散する環境では、同じセッションが別のレプリカに届いた時点で壊れます。スティッキーセッションを前提にすると、動く基盤が減ります。このサーバーのツールはどれも1リクエストで完結し、サーバー起点の通知も送らないため、セッションを持つ理由がありません。 同じ理由で POST の応答は SSE ではなく通常の JSON で返します(仕様上どちらでも構いません)。SSE はプロキシやゲートウェイにバッファされることがあり、環境依存の不具合を持ち込みやすいためです。

エラーは2種類に分かれます。

種類

返り方

スキーマ違反

JSON-RPC エラー (-32602)

電話番号の形式が inputSchema に合わない

ガードレール違反・実行時エラー

resultisError: true

ALLOWED_NUMBERS 外の宛先、レートリミット超過、Vonage API の失敗

前者は MCP SDK が inputSchema で検証して弾くため、ハンドラに到達しません(エラーメッセージにはスキーマに書いた説明がそのまま入ります)。後者は原因が reason、次に取るべき行動が suggestion に入ります。

NOTE

無効化されているツール(capability トグルが OFF)は登録されないため、tools/call では「存在しないツール」として扱われます。tools/list に出さない以上、これが MCP としての正しい表現です。どの環境変数を設定すべきかは起動ログと本 README を参照してください。

WARNING

v1.3.0 の破壊的変更: POST /mcp-invokeGET /mcp-tools を削除しました。 MCP と等価な機能を独自のインターフェースで二重に公開していたためです。/mcp だけ認証やガードレールを直しても、こうした別経路が残っていればそこから全部迂回できます。ツールの実行経路は /mcp の1本に絞りました。 既存の呼び出しは JSON-RPC の tools/call に置き換えてください。

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_sms","arguments":{"to":"09012345678","message":"hello"}}}

POST /webhooks/message-status

Vonage Messages API の Status Webhook(配信結果 / DLR)の受信エンドポイントです。Vonage から呼ばれるため Bearer トークン認証の対象外ですが、別途Webhook認証が必須です。

受信した配信結果はオンメモリに24時間保持され、get_sms_status ツールから参照できます。

IMPORTANT

このサーバーが送信した記録のない message_id は、配信ステータスの保持対象になりません。 同じ Vonage Application を別のシステムと共用していると、そちらが送信したメッセージの DLR もこのエンドポイントに届きます。それをそのまま保持すると、保持件数の上限(1000件)から自分のレコードが押し出され、get_sms_status が使えなくなります。 ただし、まれに DLR が送信 API のレスポンスより先に届くことがあります。これを取りこぼさないよう、未知の ID は 5 分間だけ別の小さなバッファに保持し、対応する送信が記録された時点で取り込みます。レスポンスの pending: true はこの状態を表します。Vonage Dashboard の Application 設定で Status URLhttps://<host>/webhooks/message-status を登録してください。

認証は以下の優先順位で行われます。

  1. VONAGE_API_SIGNATURE_SECRET が設定されていれば、Authorization: Bearer <JWT> の署名(HS256)と、ボディを束縛する payload_hash クレームを検証する(推奨

  2. VONAGE_WEBHOOK_SECRET が設定されていれば、x-webhook-secret ヘッダーと照合する

  3. どちらも未設定なら 503 を返してエンドポイントを無効化する

状況

HTTPステータス

取り込み成功

200ignored: false

再送・順序逆転で古い通知が届いた

200ignored: true、既存の状態を維持)

認証情報なし・不正な署名・payload_hash 不一致

401

message_uuid / status が欠けたペイロード

400

Webhook認証が未設定

503

# 共有シークレット方式の例
curl -X POST http://localhost:3000/webhooks/message-status \
  -H "Content-Type: application/json" \
  -H "x-webhook-secret: $VONAGE_WEBHOOK_SECRET" \
  -d '{"message_uuid":"abc-123","to":"819012345678","status":"delivered","channel":"sms"}'

POST /webhooks/inbound

受信メッセージ用のスタブ(常に 200 を返す)。Vonage側の設定必須項目を満たすために用意しています。

POST /webhooks/voice/answer

音声の着信に対して NCCO を返すエンドポイントです。このサーバーは発信専用で、着信を処理する機能を持ちません。 案内を読み上げて通話を終了します(文面は VOICE_INBOUND_MESSAGE で変更できます)。

それでも用意しているのは、Vonage の番号をアプリケーションにリンクすると、その番号への着信がアプリに向くためです。Answer URL が無いと、発信者は無言のまま切られます。

IMPORTANT

Vonage 側で Answer URL の HTTP メソッド(answer_method)を POST に変更してください。 既定は GET ですが、署名付き Webhook の検証はリクエストボディのハッシュ(payload_hash)を必要とするため、ボディの無い GET は受け付けません。GET で呼ばれた場合は 405 と対処法を返します。

POST /webhooks/voice/event

通話イベントの受信エンドポイントです。受信した内容はオンメモリに24時間保持され、get_call_status ツールのレスポンスに detailsip_code として重ねて返されます。

TIP

通話が失敗した理由が届くのは、この Webhook だけです。 Voice API の GET /v1/calls/{uuid}status しか返さず detail は常に null です。設定しておくと、原因調査が推測ではなく事実になります。

detailstatus ごとに意味が異なります(公式リファレンス)。まとめて「宛先が悪い」と解釈しないでください。

status

detail

意味

掛け直す意味があるか

failed

cannot_route

宛先がこのアカウントで未対応、またはブロック。相手の状態とは無関係

ない

failed

number_out_of_service

宛先の番号自体が使われていない(番号の確認が要る)

ない

failed

internal_error

Vonage 側の内部エラー。宛先については何も分からない

時間をおけばある

rejected

invalid_number / restricted / declined

番号が無効、キャリアまたは着信者が拒否

同じ条件では期待できない

unanswered

unavailable / timeout

相手が一時的に応答できない

時間をおけばある

get_call_status は、接続できなかった通話にこの分類に沿った note を添えます。

NOTE

detail が空でも、Webhook が未設定だとは限りません。 通知が届く前に確認した、Vonage が detail を付けなかった、サーバーが再起動した、24時間の保持期間を過ぎた——どれも同じ「空」に見えます。このサーバーからは原因を判別できないため、note も断定しません。

IMPORTANT

理由が「まだ届いていない」のか「届きようがない」のかで、正しい対処は正反対です。 届きようがない構成は2つあり、note はそれぞれを名指しして再確認を勧めません。

  • stdio 版 — Webhook を待ち受けるプロセスがありません。別プロセスの HTTP 版に Event URL を向けても、記録はそちらのメモリに入るだけで stdio 側の結果は変わりません(get_sms_status が stdio で submitted 止まりなのと同じ理由)

  • Webhook の認証が未設定VONAGE_API_SIGNATURE_SECRETVONAGE_WEBHOOK_SECRET も無い場合、エンドポイントは fail-closed で 503 を返し続けます。HTTP で動いていても記録は永久に埋まりません

受信したイベントはオンメモリに24時間保持されます(最大1000件)。このうち、このサーバーが発信していない通話(着信レグや、同じ Application を共用する別システムの通話)には専用枠 200 件を設けています。 枠を分けないと、着信が多い環境で get_call_status から引ける記録のほうが押し出されるためです。

NOTE

busy が返っても「相手が通話中」と断定はできません。 実測で、アプリケーション側の設定が不十分なときに rate 0 / 0秒の busy が返り、同じ発信元から携帯宛は繋がる、という状態が起きました。detail が無いまま結論を出さないでください。

どちらのエンドポイントも認証は /webhooks/message-status と同じです(署名付きJWT を推奨、未設定なら 503 で無効化)。

WARNING

古い Vonage アプリケーションでは、署名付き Webhook が既定で無効です。 その場合このサーバーは 401 を返し続けるので、Vonage Dashboard でアプリケーションの署名付き Webhook を有効化してください

VONAGE_WEBHOOK_SECRET(共有シークレット)は、この経路の代替にはなりません。Vonage のアプリケーション設定で指定できるのは URL と HTTP メソッドだけで、x-webhook-secret ヘッダーを付ける手段がないためです。 この方式が使えるのは、手前に置いたゲートウェイなどがヘッダーを付与する構成に限られます。

Vonage Dashboard の Application 設定で、次のように登録します。

設定項目

URL

メソッド

Answer URL

https://<host>/webhooks/voice/answer

POST

Event URL

https://<host>/webhooks/voice/event

POST

Gemini Enterprise などのAIエージェントから利用する

AIエージェントに設定すべき System Instruction(承認フロー、dry_run の使い方、エラー対処方針)を docs/gemini_system_instruction.md にまとめています。そのまま貼り付けられる形式です。

あわせて、サーバー側で ALLOWED_NUMBERSRATE_LIMIT_PER_HOUR を設定することを強く推奨します。System Instruction はプロンプトインジェクションで破られる前提で読んでください。 実効的な防御はサーバー側の設定だけです。

⚠️ Gemini Enterprise のカスタム MCP サーバーコネクタを使う場合

公式ドキュメントによれば、このコネクタが送れる認証は 「認証なし」と「OAuth 2.0」の2つだけで、任意のヘッダを設定する欄がありません。つまり MCP_AUTH_TOKEN はこの経路では使えません。

かつコネクタは、MCP サーバーが公開インターネット上の HTTPS エンドポイントで到達可能であることを要求します。

CAUTION

ここで「認証なし」を選ぶと、課金を発生させられるサーバーが無認証で全世界に公開されます。 選ばないでください。

取りうる構成は次の2つです。

構成A: 上流で OAuth 2.0 を終端する

API Gateway や Identity-Aware Proxy をこのサーバーの手前に置いて OAuth 2.0 を処理し、本サーバーには TRUST_UPSTREAM_AUTH=true を設定します。認証の実装をこのサーバーから切り離せるうえ、監査ログもプラットフォーム側の仕組みに乗せられます。詳しくは推奨構成: 認証は手前の層に置くを参照してください。

構成B: ADK でエージェントを書く(実機で確認済み・推奨)

コネクタを使わず、Agent Development KitMcpToolset から StreamableHTTPConnectionParams で接続します。任意のヘッダを送れるので MCP_AUTH_TOKEN がそのまま使えます。

ツール実行前の承認も ADK の require_confirmation で取れます。 Gemini Enterprise の Apps に承認ウィンドウが表示され、承認するまで送信されません(2026-08-25 に dry_run → 承認 → 実送信 → delivered まで確認しました)。

手順は Gemini Enterprise の Agent Apps から使う(ADK 経由) にまとめています。

import os

from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams

# 完全な実装では Secret Manager から解決する(下記リンク先を参照)
MCP_AUTH_TOKEN = os.environ["MCP_AUTH_TOKEN"]

def confirm_unless_dry_run(**kwargs) -> bool:
    """dry_run: true 以外の呼び出しに承認を要求する。"""
    return kwargs.get("dry_run") is not True

# 課金対象のツールだけを、承認を必須にして渡す
sending = McpToolset(
    connection_params=StreamableHTTPConnectionParams(
        url="https://your-server.example.com/mcp",
        headers={"Authorization": f"Bearer {MCP_AUTH_TOKEN}"},
    ),
    tool_filter=["send_sms", "make_voice_call"],
    require_confirmation=confirm_unless_dry_run,
)
WARNING

require_confirmation を省くと、課金対象のツールが承認なしで実行されます。 require_confirmation の callable にはツール名が渡らないため、読み取り専用のツールは別の McpToolset として渡してください(承認を掛けると不要な確認が毎回出ます)。

このサンプルは要点だけです。トークンをシリアライズに含めない書き方まで含めた完全な実装は ADK 経由の手順 にあります。

プロジェクト構造(続き)

├── dist/                  # コンパイルされたJavaScript
├── package.json           # プロジェクト設定
├── tsconfig.json          # TypeScript設定
├── jest.config.js         # Jest設定
├── .env.example           # 環境変数設定例
├── private.key            # Vonage秘密鍵(要設定・リポジトリには含めない)
├── LICENSE                # Apache License 2.0
├── SECURITY.md            # セキュリティポリシー
├── CONTRIBUTING.md        # コントリビューションガイド
└── README.md             # このファイル

依存関係

主要パッケージ

  • @vonage/server-sdk - Vonage SMS機能

  • @vonage/voice - Voice通話機能専用SDK

  • @vonage/jwt - Webhook の署名検証

  • @modelcontextprotocol/sdk - MCP Server実装

  • zod - スキーマ検証

  • zod-to-json-schema - ZodスキーマからJSON Schemaを生成(HTTP版の tools/list 用)

  • express / cors - HTTPラッパー

Vonage アカウントについて

このサーバーを使うには Vonage の Application ID と秘密鍵が必要です。

日本国内でご利用の場合は、日本語での申し込みページ からのお申し込みをご検討ください。日本語でのサポートと請求に対応しています。

海外からご利用の場合は Vonage Developer Portal から直接開設してください。

このサーバーはどちらの経路で開設したアカウントでも同じように動作します。 特定の経路を強制することはありません。

提供元

このプロジェクトは 株式会社KDDIウェブコミュニケーションズ が Vonage のリセラーとして開発・公開しています。

私たちはこのサーバーをサービスとして運営していません。 OSS のリファレンス実装として提供しており、利用者の資格情報を預かることはありません。

ライセンス

Apache License 2.0

Copyright 2026 KDDI Web Communications Inc.

関連ドキュメント

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    MCP server for sending SMS messages via SmsManager.cz HTTP API, supporting high, economy, and low delivery gateways.
    1
    -
  • A
    license
    B
    quality
    B
    maintenance
    MCP server for sending SMS via the SMSPM API. Send transactional SMS from Claude Desktop, Cursor, Windsurf, Cline, or any MCP client.
    1
    48
    MIT