ipbx-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ipbx-mcplist all queues"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ipbx-mcp
Servidor MCP do IPBX em TypeScript. Transporte Streamable HTTP em modo stateless, autenticação por bearer estático e/ou OAuth 2.1 + Google Workspace, persistência local em SQLite (clients OAuth, refresh tokens, audit log). Herdado do scaffold base-mcp, expõe os dados do PABX (MySQL) como tools tipadas.
URL pública em produção: https://mcp.ipbx.vivavox.com.br.
Requisitos
Node.js >= 22 (
better-sqlite3v12 precisa)Para OAuth: OAuth Client no Google Cloud Console em modo Internal
Related MCP server: MCP Manager
Instalação
npm install
cp .env.example .env # depois preencha os valores reais
npm run buildConfiguração
Carregue o .env no processo (systemd EnvironmentFile=, docker env_file:, ou node --env-file=.env na hora do start).
Obrigatórias
Pelo menos um dos caminhos de auth:
Variável | Quando usar |
| Bearer estático — Claude Desktop, CLI, API, scripts, cron |
| OAuth — clientes via claude.ai (web/mobile) |
OAuth (opcional, mas necessário pra claude.ai)
Variável | Descrição |
| URL canônica do servidor (ex: |
| Chave HS256 dos JWTs (32 bytes hex) |
| Do OAuth Client no Google Cloud Console |
| Do OAuth Client no Google Cloud Console |
| Domínio Workspace permitido (default: |
Quando todas estão presentes, as rotas /authorize, /oauth/google/callback, /token e /register (DCR) são montadas. Sem elas, só o bearer estático funciona.
Outras
Variável | Default | Descrição |
|
| Porta HTTP |
|
| Interface (use |
| — | Lista CSV de hosts aceitos no header |
|
| Caminho do arquivo SQLite |
MySQL (fonte de dados do IPBX)
Variável | Default | Descrição |
| — | Host do MySQL |
|
| |
| — | Use um usuário dedicado com |
| — | |
| — | |
|
| Tamanho do pool ( |
| vazio | Qualquer valor liga TLS com verificação de cert |
| — | Tenant que esta instância atende (ver abaixo) |
O banco é multi-tenant — uma instância Asterisk por cliente, tabela ipbx — mas cada instância do MCP atende um tenant só. Todas as queries filtram por IPBX_ID, e nenhuma tool aceita esse id como parâmetro: assim o isolamento entre clientes não depende do que o modelo passa na chamada. Um container e um subdomínio por tenant.
Gere tokens aleatórios com:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Endpoints
Método | Path | Auth | Descrição |
POST |
| bearer | JSON-RPC do MCP via Streamable HTTP |
GET |
| bearer |
|
DELETE |
| bearer |
|
GET |
| público |
|
GET |
| público | RFC 8414 metadata |
GET |
| público | RFC 9728 metadata |
POST |
| público | Dynamic Client Registration (RFC 7591) |
GET |
| público | Redireciona pro Google |
GET |
| público | Recebe o redirect do Google |
POST |
| público |
|
401 no /mcp inclui WWW-Authenticate: Bearer realm=..., resource_metadata=... — sem isso claude.ai não descobre o AS no primeiro contato.
Tools disponíveis
Nome das tools segue ipbx_<model>_<action>, com <action> no vocabulário list / get / search / count.
ipbx_instance_get
Dados de cadastro da instância IPBX que este servidor atende — nome, IP e portas SIP/AMI.
Parâmetros: nenhum. A instância é fixa, definida por IPBX_ID no ambiente.
Retorno:
{
"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"
}Devolve isError se o IPBX_ID configurado não existir na tabela ipbx.
ipbx_branch_list
Lista os ramais da instância.
Parâmetros:
search(string, opcional): busca parcial por número do ramal ou nomelimit(number, opcional): 1–500, default100
Retorno:
{
"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
}
]
}Não retorna as credenciais SIP. As colunas password (senha em claro) e username (identificador de autenticação, diferente do número do ramal) ficam de fora por design — juntas permitem registrar um softphone e originar chamadas na conta do cliente. A lista de colunas no SELECT é explícita justamente para que nenhuma delas entre por descuido.
ipbx_user_list
Lista os usuários do painel da instância.
Parâmetros:
search(string, opcional): busca parcial por nome ou emaillimit(number, opcional): 1–500, default100
Retorno:
{
"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"
}
]
}Não retorna a senha de acesso. A coluna secret fica de fora: é a senha de login do painel, guardada em texto puro no banco (sem hash). Expor isso entregaria acesso administrativo ao PABX.
ipbx_group_list
Lista os grupos de ramais da instância, com quantos ramais cada um tem.
Parâmetros:
search(string, opcional): busca parcial por nome ou descriçãolimit(number, opcional): 1–500, default100
Retorno:
{
"total": 6,
"truncated": false,
"groups": [
{
"id": 1,
"name": "Suporte",
"description": "Grupo do suporte",
"branches": 11
}
]
}A tabela groups não guarda credenciais — ao contrário de branch e users, aqui todas as colunas são expostas.
ipbx_trunk_list
Lista os troncos da instância.
Parâmetros:
search(string, opcional): busca parcial por nome ou hostlimit(number, opcional): 1–500, default100
Retorno:
{
"total": 2,
"truncated": false,
"trunks": [
{
"id": 1,
"name": "Vivavox",
"host": "sip.vivavox.com.br",
"port": "5060",
"register": true,
"record": true,
"auth": "credentials"
}
]
}Não retorna as credenciais da operadora. username e password ficam de fora — são a credencial mais valiosa do banco, já que permitem originar chamadas direto pela operadora, tarifadas na conta. No lugar delas vai auth, que diz apenas como o tronco autentica: "credentials" (usuário/senha) ou "ip" (allowlist de IP, sem senha).
ipbx_queue_list
Lista as filas de atendimento, com a estratégia de distribuição e quantos membros cada uma tem.
Parâmetros: search (string, opcional), limit (1–500, default 100)
{
"total": 5,
"queues": [
{ "id": 1, "name": "Suporte", "strategy": "ringall", "members": 8 },
{ "id": 5, "name": "Teste", "strategy": "leastrecent", "members": 1 }
]
}ipbx_queue_member_list
Lista os membros das filas, na ordem de toque.
Parâmetros:
queue_id(number, opcional): filtra uma fila; omita para trazer todaslimit(number, opcional): 1–500, default200
Retorno:
{
"total": 8,
"members": [
{
"queue_id": 1,
"queue": "Suporte",
"position": 1,
"type": "branch",
"exten": "29",
"name": "Mateus Damaceno",
"ref": "branch-10"
}
]
}A coluna queue_member.member guarda uma referência no formato <tipo>-<id> — branch-10 aponta para o branch.id 10, que é o ramal 29. Não é o número do ramal. A tool resolve isso para exten + name quando o membro é um ramal. Nem todo membro é: existem entradas redirect-N, que voltam com type: "redirect" e exten/name nulos.
ipbx_ivr_list
Lista as URAs, com o áudio associado e a transcrição do que é falado para quem liga.
Parâmetros: search (string, opcional — casa no nome ou no texto da transcrição), limit (1–500, default 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
}
]
}A transcrição é o campo mais útil: permite achar uma URA pelo que ela diz, não só pelo nome.
ipbx_ivr_option_list
Lista as opções das URAs — qual tecla leva a qual destino.
Parâmetros:
ivr_id(number, opcional): filtra uma URA; omita para trazer todaslimit(number, opcional): 1–500, default200
Retorno:
{
"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 é polimórfico: aponta para 5 tabelas diferentes (branch, queue, ivr, redirect, app) no formato <tipo>-<id>, e ainda aceita literais sem id (internal). A tool resolve o nome do destino em todos os casos; literais voltam com name nulo e o ref preservado.
O campo digit nem sempre é um dígito: t é timeout e padrões como 7X casam faixas de ramal.
ipbx_redirect_list
Lista os redirects — ramais curtos que encaminham para um número externo saindo por um tronco. São os mesmos redirect-<id> que aparecem como destino em filas, URAs e regras de roteamento.
Parâmetros: search (string, opcional — casa ramal, nome ou número), limit (1–500, default 100)
{
"total": 12,
"redirects": [
{
"id": 2,
"exten": "73",
"name": "Ricardo Landim",
"forward": "5535988023317",
"trunk": "Vivavox",
"ref": "redirect-2"
}
]
}⚠️ Dado pessoal. forward é um número de celular pessoal em 100% das linhas — não é credencial, mas é dado pessoal sob LGPD. A tool o retorna porque é a razão de existir da tabela, mas ele não vai para o audit_log.
ipbx_routing_list
Lista os planos de roteamento, com quantas regras e janelas de horário cada um tem.
Parâmetros: search (string, opcional), limit (1–500, default 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
Lista as janelas de horário dos planos.
Parâmetros: routing_id (number, opcional), limit (1–500, default 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"]
}O pattern é armazenado no formato do Asterisk, uma faixa por linha; a tool devolve como lista.
ipbx_routing_rule_list
Lista as regras de roteamento — o dialplan. Cada regra casa um padrão de número dentro de uma janela de horário, suprime dígitos, adiciona prefixo e envia ao destino.
Parâmetros: routing_id (number, opcional), limit (1–500, default 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 é polimórfico como o da URA, mais o tipo trunk (usado nas regras de saída) — seis destinos possíveis no total.
Dois detalhes do schema tratados aqui: a coluna do banco chama-se supress (com um "p"), exposta como suppress; e goto2/goto3 existem mas estão vazias em todas as linhas — aparecem como goto_extra apenas se algum dia forem preenchidas.
Toda chamada gera uma linha em audit_log com a identidade do chamador: email Google se JWT, service:static se bearer estático.
Comandos
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 InspectorSmoke test local:
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"}'Deploy
Docker (recomendado)
Dockerfile multi-stage (node:22-slim), runtime como user não-root mcp, expõe /data como volume pro SQLite, healthcheck via /health. Em produção o deploy é automático via .github/workflows/deploy.yml (push de tag vX.Y.Z → build no GHCR → docker run na VPS). Manualmente:
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-mcpBackup do SQLite:
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= é o equivalente nativo do systemd para .env. Use um usuário dedicado (mcp) em vez de root.
Configurando em um cliente MCP
Claude Desktop / CLI (bearer estático)
{
"mcpServers": {
"ipbx": {
"type": "http",
"url": "https://mcp.ipbx.vivavox.com.br/mcp",
"headers": {
"Authorization": "Bearer SEU_MCP_AUTH_TOKEN"
}
}
}
}claude.ai (OAuth)
Adicionar como Custom Connector usando https://mcp.ipbx.vivavox.com.br/mcp. O flow OAuth dispara automaticamente — claude.ai descobre o AS via WWW-Authenticate, registra um client via DCR, redireciona pro Google, recebe o code e troca por um access token.
Estrutura
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)
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.
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/ricardolan85/ipbx-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server