ipbx-mcp
ipbx-mcp
IPBX の MCP サーバー(TypeScript 製)。トランスポートは Streamable HTTP(ステートレスモード)、認証は 静的 bearer および/または OAuth 2.1 + Google Workspace、ローカル永続化は SQLite(OAuth クライアント、リフレッシュトークン、監査ログ)。base-mcp スキャフォールドから継承し、PABX(MySQL)のデータを型付きツールとして公開します。
本番公開 URL: https://mcp.ipbx.vivavox.com.br。
要件
Node.js >= 22(
better-sqlite3v12 が必要)OAuth の場合: Google Cloud Console の OAuth クライアント(内部モード)
Related MCP server: utel-mcp
インストール
npm install
cp .env.example .env # depois preencha os valores reais
npm run build設定
.env をプロセスに読み込ませます(systemd の EnvironmentFile=、docker の env_file:、または起動時に node --env-file=.env)。
必須
認証パスの少なくとも 1 つ:
変数 | 使用時 |
| 静的 bearer — Claude Desktop、CLI、API、スクリプト、cron |
| OAuth — claude.ai(web/mobile)経由のクライアント |
OAuth(任意、ただし claude.ai には必要)
変数 | 説明 |
| サーバーの正規 URL(例: |
| JWT の HS256 キー(32 バイト hex) |
| Google Cloud Console の OAuth クライアントから |
| Google Cloud Console の OAuth クライアントから |
| 許可する Workspace ドメイン(デフォルト: |
これらがすべて存在する場合、/authorize、/oauth/google/callback、/token、/register(DCR)のルートがマウントされます。存在しない場合は、静的 bearer のみが機能します。
その他
変数 | デフォルト | 説明 |
|
| HTTP ポート |
|
| インターフェース(ローカル開発では |
| — |
|
|
| SQLite ファイルのパス |
| — | 録音を提供する IPBX API のベース(例: |
MySQL(IPBX のデータソース)
変数 | デフォルト | 説明 |
| — | MySQL ホスト |
|
| |
| — |
|
| — | |
| — | |
|
| プールサイズ( |
| 空 | 任意の値で証明書検証付き TLS が有効 |
| — | このインスタンスが担当するテナント(下記参照) |
データベースはマルチテナントです(顧客ごとに Asterisk インスタンス、テーブル ipbx)が、各 MCP インスタンスは 1 つのテナントのみを担当します。すべてのクエリは IPBX_ID でフィルタリングされ、どのツールもこの ID をパラメータとして受け取りません。これにより、顧客間の分離はモデルが呼び出し時に渡すものに依存しません。テナントごとに 1 つのコンテナと 1 つのサブドメインを使用します。
ランダムなトークンを生成するには:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"エンドポイント
メソッド | パス | 認証 | 説明 |
POST |
| bearer | Streamable HTTP 経由の MCP JSON-RPC |
GET |
| bearer |
|
DELETE |
| bearer |
|
GET |
| 公開 |
|
GET |
| 公開 | RFC 8414 メタデータ |
GET |
| 公開 | RFC 9728 メタデータ |
POST |
| 公開 | Dynamic Client Registration (RFC 7591) |
GET |
| 公開 | Google にリダイレクト |
GET |
| 公開 | Google からのリダイレクトを受け取る |
POST |
| 公開 |
|
/mcp の 401 には WWW-Authenticate: Bearer realm=..., resource_metadata=... が含まれます。これがないと、claude.ai は最初の接触で AS を発見できません。
利用可能なツール
ツール名は ipbx_<model>_<action> に従い、<action> は list / get / search / count の語彙を使用します。
ipbx_instance_get
このサーバーが担当する IPBX インスタンスの登録データ — 名前、IP、SIP/AMI ポート。
パラメータ: なし。インスタンスは固定で、環境の IPBX_ID で定義されます。
戻り値:
{
"id": 1,
"shortname": "vivavox",
"fullname": "Vivavox Telecom",
"ipaddr": "138.94.55.155",
"sipport": 5601,
"amiport": 6501,
"created": "2024-06-17T16:37:59.000Z",
"updated": "2024-06-17T16:37:59.000Z"
}設定された IPBX_ID が ipbx テーブルに存在しない場合、isError を返します。
ipbx_branch_list
インスタンスの内線番号を一覧表示します。
パラメータ:
search(string、任意): 内線番号または名前の部分一致検索limit(number、任意): 1〜500、デフォルト100
戻り値:
{
"total": 27,
"truncated": false,
"branches": [
{
"id": 2,
"exten": "23",
"name": "Ricardo Landim",
"group": "Suporte",
"record": true,
"webrtc": false,
"dtmf": "rfc4733",
"forward_busy": "035988023317",
"forward_noanswer": "035988023317",
"forward_noanswer_wait": 5
}
]
}SIP 資格情報は返しません。 password(平文のパスワード)と username(内線番号とは異なる認証識別子)の列は設計上除外されています。これらが揃うと、ソフトフォンを登録して顧客のアカウントで発信できてしまうためです。SELECT の列リストは、うっかり含まれないように明示的です。
ipbx_user_list
インスタンスのパネルユーザーを一覧表示します。
パラメータ:
search(string、任意): 名前またはメールの部分一致検索limit(number、任意): 1〜500、デフォルト100
戻り値:
{
"total": 6,
"truncated": false,
"users": [
{
"id": 11,
"name": "Suporte",
"email": "suporte@vivavox.com.br",
"created": "2024-07-10T13:56:41.000Z",
"updated": "2024-07-10T13:56:41.000Z"
}
]
}アクセスパスワードは返しません。 secret 列は除外されています。これはパネルのログインパスワードで、データベースに平文で保存されています(ハッシュなし)。これを公開すると、PABX への管理アクセスを提供することになります。
ipbx_group_list
インスタンスの内線グループを、各グループの内線数とともに一覧表示します。
パラメータ:
search(string、任意): 名前または説明の部分一致検索limit(number、任意): 1〜500、デフォルト100
戻り値:
{
"total": 6,
"truncated": false,
"groups": [
{
"id": 1,
"name": "Suporte",
"description": "Grupo do suporte",
"branches": 11
}
]
}groups テーブルは資格情報を保存しません — branch や users とは異なり、ここではすべての列が公開されます。
ipbx_trunk_list
インスタンスのトランクを一覧表示します。
パラメータ:
search(string、任意): 名前またはホストの部分一致検索limit(number、任意): 1〜500、デフォルト100
戻り値:
{
"total": 2,
"truncated": false,
"trunks": [
{
"id": 1,
"name": "Vivavox",
"host": "sip.vivavox.com.br",
"port": "5060",
"register": true,
"record": true,
"auth": "credentials"
}
]
}キャリアの資格情報は返しません。 username と password は除外されています。これらはデータベースで最も価値の高い資格情報であり、キャリア経由で直接発信でき、アカウントに課金されるためです。代わりに auth が返され、トランクがどのように認証するかのみを示します: "credentials"(ユーザー名/パスワード)または "ip"(IP の許可リスト、パスワードなし)。
ipbx_queue_list
着信キューを、配信戦略と各キューのメンバー数とともに一覧表示します。
パラメータ: search(string、任意)、limit(1〜500、デフォルト 100)
{
"total": 5,
"queues": [
{ "id": 1, "name": "Suporte", "strategy": "ringall", "members": 8 },
{ "id": 5, "name": "Teste", "strategy": "leastrecent", "members": 1 }
]
}ipbx_queue_member_list
キューのメンバーを、呼び出し順に一覧表示します。
パラメータ:
queue_id(number、任意): 特定のキューにフィルタリング。省略するとすべて返しますlimit(number、任意): 1〜500、デフォルト200
戻り値:
{
"total": 8,
"members": [
{
"queue_id": 1,
"queue": "Suporte",
"position": 1,
"type": "branch",
"exten": "29",
"name": "Mateus Damaceno",
"ref": "branch-10"
}
]
}queue_member.member 列は <type>-<id> 形式の参照を保持します — branch-10 は branch.id 10 を指し、これは内線 29 です。内線番号ではありません。 ツールはメンバーが内線の場合、これを exten + name に解決します。すべてのメンバーが内線とは限りません: redirect-N エントリもあり、これらは type: "redirect" として返され、exten/name は null です。
ipbx_ivr_list
IVR を、関連する音声と発信者に話される内容の文字起こしとともに一覧表示します。
パラメータ: search(string、任意 — 名前または文字起こしテキストに一致)、limit(1〜500、デフォルト 100)
{
"total": 1,
"ivrs": [
{
"id": 5,
"name": "URA Rompimento",
"audio": "URA Rompimento",
"transcription": "Olá, se você está com falta de conexão e o LED Loss do seu modem óptico...",
"options": 1
}
]
}文字起こしは最も有用なフィールドです。名前だけでなく、IVR が話す内容で IVR を見つけることができます。
ipbx_ivr_option_list
IVR のオプション — どのキーがどの宛先につながるかを一覧表示します。
パラメータ:
ivr_id(number、任意): 特定の IVR にフィルタリング。省略するとすべて返しますlimit(number、任意): 1〜500、デフォルト200
戻り値:
{
"total": 7,
"options": [
{
"ivr_id": 1,
"ivr": "URA Principal - Horario comercial",
"digit": "1",
"goto": { "type": "queue", "name": "Financeiro", "exten": null, "ref": "queue-3" }
},
{
"ivr_id": 1,
"ivr": "URA Principal - Horario comercial",
"digit": "7X",
"goto": { "type": "internal", "name": null, "exten": null, "ref": "internal" }
}
]
}ivr_option.goto は多態的です: <type>-<id> 形式で 5 つの異なるテーブル(branch、queue、ivr、redirect、app)を指し、さらに ID なしのリテラル(internal)も受け入れます。ツールはすべての場合で宛先名を解決します。リテラルは name が null で返され、ref は保持されます。
digit フィールドは常に数字とは限りません: t はタイムアウトで、7X のようなパターンは内線番号の範囲に一致します。
ipbx_redirect_list
リダイレクト — トランク経由で外部番号に転送する短い内線番号を一覧表示します。これらはキュー、IVR、ルーティングルールの宛先として表示されるのと同じ redirect-<id> です。
パラメータ: search(string、任意 — 内線、名前、または番号に一致)、limit(1〜500、デフォルト 100)
{
"total": 12,
"redirects": [
{
"id": 2,
"exten": "73",
"name": "Ricardo Landim",
"forward": "5535988023317",
"trunk": "Vivavox",
"ref": "redirect-2"
}
]
}⚠️ 個人データ。 forward は 100% の行で個人の携帯電話番号です。資格情報ではありませんが、LGPD に基づく個人データです。ツールはテーブルの存在理由であるためこれを返しますが、audit_log には含まれません。
ipbx_routing_list
ルーティングプランを、各プランのルール数と時間枠数とともに一覧表示します。
パラメータ: search(string、任意)、limit(1〜500、デフォルト 100)
{
"total": 2,
"routings": [
{ "id": 1, "name": "Entrada - Padrão", "rules": 6, "time_windows": 3 },
{ "id": 2, "name": "Saida - Padrão", "rules": 8, "time_windows": 1 }
]
}ipbx_routing_time_list
プランの時間枠を一覧表示します。
パラメータ: routing_id(number、任意)、limit(1〜500、デフォルト 100)
{
"id": 1,
"routing": "Entrada - Padrão",
"name": "Horario comercial",
"ranges": ["08:00-18:00,mon", "08:00-18:00,tue", "08:00-12:00,sat"]
}pattern は Asterisk 形式で保存され、1 行に 1 つの範囲が含まれます。ツールはリストとして返します。
ipbx_routing_rule_list
ルーティングルール — ダイヤルプランを一覧表示します。各ルールは時間枠内の番号パターンに一致し、数字を削除し、プレフィックスを追加して宛先に送信します。
パラメータ: routing_id(number、任意)、limit(1〜500、デフォルト 200)
{
"id": 4,
"routing": "Saida - Padrão",
"name": "LDN",
"time_window": "Geral",
"match": "0ZZ.",
"suppress": 1,
"prefix": "55",
"goto": { "type": "trunk", "name": "Vivavox", "exten": null, "ref": "trunk-1" }
}goto1 は IVR のものと同様に多態的ですが、さらに trunk タイプ(発信ルールで使用)があります — 合計 6 つの可能な宛先です。
ここで処理されるスキーマの 2 つの詳細: データベースの列は supress(p が 1 つ)と呼ばれ、suppress として公開されます。また、goto2/goto3 は存在しますが、すべての行で空です — 将来入力された場合にのみ goto_extra として表示されます。
ipbx_cdr_list
通話履歴。期間は必須で、31 日に制限されています。cdr には PK 以外のインデックスがないため、すべてのフィルタはフルスキャンです(現在約 268k 行)。
パラメータ: date_from と date_to(YYYY-MM-DD、必須)、scope(call | leg、デフォルト call)、src と dst(部分一致)、branch_id、trunk_id、answered(bool)、call_id、limit(1–500、デフォルト 25)
{
"call_id": "sip1-1787578699.251937",
"started": "2026-08-24 10:38:19",
"ended": "2026-08-24 10:42:06",
"direction": "inbound",
"from": { "type": "trunk", "id": 1, "name": "Vivavox" },
"caller": "35997609940",
"dialed": null,
"context": "queue-3",
"answered": true,
"talk_seconds": 265,
"ring_attempts": 6,
"targets": [{ "type": "branch", "id": 16, "exten": "35", "name": "Ester Vilela" }],
"answered_by": [{ "type": "branch", "id": 16, "exten": "35", "name": "Ester Vilela" }],
"dispositions": ["ANSWERED", "NO ANSWER"],
"has_recording": true,
"legs": 10
}cdr はPABXで唯一 ipbx_id を持たないテーブルです。テナントとの紐付けはAsteriskの systemname で、ipbx-apiはこれを sip<ipbx_id> として書き込み、Asteriskは各行の uniqueid/linkedid にスタンプします。フィルタは uniqueid LIKE 'sip<id>-%' で、ハイフン付きです(ハイフンがないと、sip1 が sip10- にもマッチしてしまいます)。
1回の通話は多数の行になります。uniqueid はチャネルを識別し、linkedid は通話を識別し、Dialの試行ごとに行が生成されます。キューへの着信は22行に達します。scope=call は linkedid でグループ化します。宛先が Local/ チャネルであるレッグはキューが各メンバーを鳴らしている部分(ring_attempts になります)で、それ以外は通話(talk_seconds に加算)です。scope=leg は生のレッグを返します。通話のデバッグには call_id と併用してください。
has_recording は ANSWERED に加えて rec が入力されていることを要求します。パネルの recAvailable と同じルールです。rec 列は Dial の前に書き込まれます(ダイヤルプランは prerouting で MixMonitor を設定します)。つまり「録音が準備された」ことを示し、「音声が存在する」ことを示すわけではありません。単独では、通話の99.96%で録音ありと判定されてしまいます。
チャネル列は生のまま出力されません。channel、dstchannel、lastdata にはエンドポイントの username(SIP資格情報の半分)が入り、src は内線通話で同じ username を持ちます。すべて src/channel.ts を経由して、内線/トランク/キューとして出力されます。rec も除外され、has_recording になります。
branch_id/trunk_id によるフィルタは、行単位ではなくセミジョインで通話を選択します。集計値は引き続き通話全体を表し、その内線のレッグだけを表すわけではありません。
ipbx_recording_get
ipbx_cdr_list が返す call_id から、1件の通話の音声URLを取得します。
パラメータ: call_id(文字列、必須)
{
"call_id": "sip1-1787577145.251772",
"started": "2026-08-24 10:12:25",
"has_recording": true,
"url": "https://ipbx.vivavox.com.br/api/call/record/sip1-8f0e5161….wav",
"note": "URL publica e sem expiracao: o nome do arquivo e a unica credencial. …"
}ipbx_cdr_list のフィールドではなく独立したツールである理由は次のとおりです。ipbx-apiの /call/record ルートは認証を要求せず、URLは期限切れになりません。ファイル名(SHA1)がそのまま資格情報だからです。リストのフィールドとして実装すると、CDRの各通話がコンテキストに25件の永続的なアクセスを放出し、そのほとんどが使われることはなく、監査は25件の資格情報を記録するか、何も記録しないかのどちらかになります。録音ごとに1つのツールを使うことで、誰が要求したかの監査証跡が1行で得られます。CDRの has_recording が発見のシグナルであり、このツールがアクセスです。
音声がない場合、応答は単に拒否するのではなく理由を示します。Chamada nao atendida(録音は Dial の前に準備されるため)または内線で録音がオフになっている場合です。別のテナントの call_id は isError を返します。IPBX_ID によるフィルタがクエリに適用され、URLの sip<id> は環境から取得され、受信した call_id から取得されることはありません。
IPBX_RECORD_BASE_URL に依存します。これがない場合、サーバーは通常どおり起動し、このツールだけが明示的なメッセージとともに失敗します。サーバー上でツールを無効化するのと同じです。
すべての通話は、呼び出し元のIDとともに audit_log に行を生成します。JWTの場合はGoogleメール、静的ベアラーの場合は service:static です。ipbx_cdr_list の src/dst は電話番号であり、監査には含まれません。number_filter: true のみが残ります。
コマンド
npm run build # tsc
npm run check # tsc --noEmit (sem emitir)
npm run dev # tsc --watch
npm start # node dist/index.js
npm run inspect # MCP Inspectorローカルでのスモークテスト:
curl -s http://localhost:3000/health
curl -s http://localhost:3000/.well-known/oauth-authorization-server
curl -s -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $MCP_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'デプロイ
Docker(推奨)
マルチステージ Dockerfile(node:22-slim)、非rootユーザー mcp として実行、SQLite用に /data をボリュームとして公開、/health によるヘルスチェック。本番環境では .github/workflows/deploy.yml により自動デプロイされます(vX.Y.Z タグのプッシュ → GHCRでビルド → VPSで docker run)。手動の場合:
docker image build . -t ipbx-mcp:1.0
docker container run -d --env-file .env -p 50020:3000 \
-v ipbx_data:/data --restart unless-stopped --name ipbx-mcp ipbx-mcp:1.0
docker stop ipbx-mcp && docker rm ipbx-mcp
docker logs -f ipbx-mcpSQLiteのバックアップ:
docker run --rm \
-v ipbx_data:/data \
-v $PWD:/backup \
alpine tar czf /backup/sqlite-bkp.tgz -C /data .systemd
[Unit]
Description=ipbx-mcp
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/local/ipbx-mcp
ExecStart=/usr/bin/node dist/index.js
EnvironmentFile=/var/local/ipbx-mcp/.env
User=mcp
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetEnvironmentFile= は、systemdにおける .env のネイティブな相当物です。root ではなく専用ユーザー(mcp)を使用してください。
MCPクライアントでの設定
Claude Desktop / CLI(静的ベアラー)
{
"mcpServers": {
"ipbx": {
"type": "http",
"url": "https://mcp.ipbx.vivavox.com.br/mcp",
"headers": {
"Authorization": "Bearer SEU_MCP_AUTH_TOKEN"
}
}
}
}claude.ai(OAuth)
https://mcp.ipbx.vivavox.com.br/mcp を使用してカスタムコネクタとして追加します。OAuthフローは自動的に開始されます。claude.ai は WWW-Authenticate を介してASを発見し、DCRを介してクライアントを登録し、Googleにリダイレクトし、コードを受け取り、アクセストークンと交換します。
構造
src/
index.ts # bootstrap HTTP, leitura de env, registro de rotas
server.ts # createServer() registra as tools (ipbx_*)
mysql.ts # pool mysql2 + queries do IPBX (tenant fixo)
channel.ts # nome de canal do Asterisk -> ramal/tronco/fila
sqlite.ts # better-sqlite3 + apply schemas
audit.ts # logToolCall() -> audit_log
auth/
jwt.ts # sign/verify HS256 (jose)
middleware.ts # requireAuth: JWT -> fallback bearer estático
oauth/
routes.ts # registerOAuthRoutes()
store.ts # DCR clients, codes, refresh, authorize-tx
google.ts # OAuth do Google (authorize URL + token exchange)
pkce.ts # verificação S256 em tempo constante
sql/
001_oauth_schema.sql # oauth_clients, oauth_codes, oauth_refresh_tokens, audit_log
002_oauth_authorize_tx.sql # oauth_authorize_tx (state Google <-> params)
Dockerfile
.github/workflows/deploy.yml # build GHCR + deploy SSH na VPSThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Query, browse, and automate OmegaAI workspaces from any MCP client. Streamable HTTP with OAuth 2.0.
The official MCP Server from Mia-Platform to interact with Mia-Platform Console
Streamable HTTP MCP server exposing planner flows, tasks, and squads.
MCP server for Codat — companies, connections, invoices, bills and financial statements.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceRemote MCP server for Odoo ERP — exposes Odoo operations over Streamable HTTP with bearer token authentication.MIT
- FlicenseAqualityBmaintenanceAn MCP server that wraps the UTEL IP-telephony REST API as MCP tools, enabling LLM agents to make authenticated HTTP requests to the UTEL API via a simple tool interface.11
- FlicenseNot gradedqualityDmaintenanceA standalone MCP server that exposes API endpoints as tools for AI assistants, using SSE transport.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/paralelum/ipbx-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server