Servidor MCP
Conecte um assistente de IA ao seu workspace — como autorizar, o que ele pode fazer e todas as ferramentas que recebe.
O Pingo Notify fala MCP (Model Context Protocol), então um assistente de IA pode operar um workspace direto: listar conexões, enviar mensagens de WhatsApp, ler uma conversa, gerenciar webhooks. Você aprova o que ele pode fazer uma vez, numa tela de consentimento, e ele nunca manuseia um token de API.
Conectar
Adicione uma URL ao cliente, como conector customizado ou remoto:
https://api.pingonotify.com/v3/mcp
Tudo depois disso é o protocolo fazendo o trabalho dele:
O cliente descobre o servidor
A primeira chamada dele chega sem credencial e recebe um 401 com WWW-Authenticate: Bearer …, resource_metadata="…/.well-known/oauth-protected-resource/v3/mcp". Esse documento diz qual é o servidor de autorização e, de propósito, apenas os escopos que este servidor realmente usa.
Ele se registra sozinho
POST /v3/oauth/register. O registro não tem burocracia: um conector de IA se registra como cliente público — sem segredo, com PKCE no lugar.
Você autoriza
O cliente abre /v3/oauth/authorize. Você entra na conta, escolhe o workspace e aprova os escopos que ele pediu. Este é o único passo com uma pessoa dentro.
Ele recebe um token
POST /v3/oauth/token troca o código, e daí em diante toda chamada leva Authorization: Bearer ….
Só um Owner ou um Admin pode autorizar uma aplicação para um workspace. A tela de consentimento oferece apenas os workspaces em que o seu papel permite; um Manager ou um Agent não consegue conectar um assistente ao workspace do time. Autorizar sem escolher workspace amarra o token à sua conta pessoal.
Conectar não exige plano nenhum, e continua funcionando com assinatura expirada — mas enviar não. Veja Limites.
O que o assistente recebe
Um workspace, fixado no consentimento
Tudo que o assistente enxerga pertence ao workspace que você escolheu ao autorizar. Não há ferramenta para trocar de workspace nem header que ele possa mandar para mudar isso: uma requisição que tentar recebe 403 — This access token is bound to a different workspace; re-authorize to switch. Um segundo workspace significa uma segunda autorização.
Escopo é teto, nunca concessão
A permissão efetiva de um token é a interseção entre os escopos que você aprovou e o que você pode fazer naquele workspace. Aprovar messages:send não deixa um assistente enviar se o seu próprio papel não pode; escopo só estreita.
| Escopo | O que o assistente alcança |
|---|---|
profile | Quem autorizou, e em qual workspace |
connections:read | Suas conexões e o status delas, conversas, templates, consulta de número |
messages:read | Histórico de mensagens e a mídia de uma mensagem recebida |
messages:send | Envio — todos os tipos de mensagem abaixo, e o indicador de "digitando" |
webhooks:read | Seus webhooks, o catálogo de eventos, as tentativas de entrega |
webhooks:write | Criar, editar e excluir webhooks |
Você pode sempre aprovar menos do que foi pedido. Reaprovar com menos escopos revoga os tokens já emitidos para aquela integração, para que a tela e a realidade nunca discordem.
connections:write pode aparecer na tela de consentimento também, porque ele igualmente satisfaz "ler uma conexão". Nenhuma ferramenta deste catálogo precisa dele — aprovar só connections:read basta.
Ele conhece o próprio limite antes de tentar
O handshake carrega um parágrafo montado para aquele token: as ferramentas que este acesso pode usar e, pelo nome, as que não pode. Um assistente que já sabe que lhe falta webhooks:write avisa você em vez de gastar um turno descobrindo.
Ferramenta fora de escopo continua listada de propósito. Esconder trocaria uma recusa que diz qual escopo pedir por um inútil "ferramenta desconhecida".
As ferramentas
Vinte e três ferramentas. O corte é por domínio, não por rota — o helpdesk está fora por decisão de produto.
Orientação
| Ferramenta | Escopo | Conexão |
|---|---|---|
get_workspace | profile | qualquer |
list_connections | connections:read | qualquer |
check_whatsapp_number | connections:read | só QR |
list_chats | connections:read | só QR |
list_message_templates | connections:read | só Oficial |
get_message_template | connections:read | só Oficial |
Comece por list_connections, sempre. Toda outra ferramenta precisa de um connectionId, e é ela que revela o tipo da conexão — que decide se uma ferramenta de envio funciona. Ela também informa canSend, verdadeiro só enquanto a conexão está open.
check_whatsapp_number aceita até 50 números por chamada e devolve o id canônico de cada um. get_workspace conta ao assistente quais escopos ele de fato tem.
Mensageria
| Ferramenta | Escopo | Conexão | |
|---|---|---|---|
send_whatsapp_message | messages:send | qualquer | custa dinheiro |
send_whatsapp_media | messages:send | qualquer | custa dinheiro |
send_whatsapp_voice_note | messages:send | qualquer | custa dinheiro |
send_whatsapp_sticker | messages:send | só QR | custa dinheiro |
send_whatsapp_template | messages:send | só Oficial | custa dinheiro |
send_whatsapp_list | messages:send | só QR | custa dinheiro |
send_whatsapp_buttons | messages:send | só QR | custa dinheiro |
set_typing_presence | messages:send | só QR | plano pago |
list_chat_history | messages:read | qualquer | — |
download_message_media | messages:read | qualquer | — |
prepare_media_upload | messages:send | qualquer | abre um envio |
Há uma ferramenta por forma de mensagem, em vez de uma ferramenta com um campo type. Um único schema com sete ramos opcionais deixa o modelo montar uma combinação que o WhatsApp recusa — e esse erro chega depois de o envio ter sido cobrado.
Webhooks
| Ferramenta | Escopo | |
|---|---|---|
list_webhook_event_types | webhooks:read | o catálogo, com uma nota de confiabilidade por evento |
list_webhooks | webhooks:read | — |
list_webhook_deliveries | webhooks:read | o que o Pingo de fato tentou entregar |
create_webhook | webhooks:write | — |
update_webhook | webhooks:write | destrutivo: a lista de eventos substitui, não soma |
delete_webhook | webhooks:write | destrutivo |
Regras que falham em silêncio
Vale ler antes de soltar um assistente: ignorar estas não produz erro nenhum, só um resultado que ninguém recebe.
O tipo da conexão decide o que é possível. Uma conexão QR envia figurinha, lista, botões e o indicador de "digitando", confere se um número existe no WhatsApp, e é a única que alimenta webhook com o conjunto completo de eventos. Uma conexão Oficial (Cloud API da Meta) envia template aprovado — o único jeito de iniciar conversa fora da janela de 24 horas — e não faz nada da lista acima. Uma lista ou um conjunto de botões enviados pelo canal oficial chegam como texto puro, sem os botões, e ninguém recebe erro.
- Conexão oficial alimenta menos o webhook. Mensagens recebidas (
messages.upsert) e atualizações de entrega (messages.update) chegam sim;send.message,messages.delete,messages.edited,presence.updateeconnection.updatenunca chegam. Inscrever-se nesses numa conexão oficial produz silêncio, não recusa. - Dois eventos são aceitos no cadastro e nunca entregues em conexão nenhuma:
connection.updateepresence.update. Olist_webhook_event_typescarrega essa verdade por evento, e é por isso que o assistente deve chamá-lo antes de criar uma inscrição. Para status de conexão, faça polling emlist_connections. - Webhook sem conexão vinculada nunca dispara. O
create_webhookexige pelo menos um id de conexão — passe todos se você quer o workspace inteiro. O webhook nasce ativo e olist_webhook_deliveriesvolta vazio, o que se lê exatamente como "evento errado". - Seu endpoint vai ver o mesmo evento mais de uma vez. A entrega estoura em 10 segundos e é retentada até três vezes, com uma linha registrada por tentativa. Faça o endpoint idempotente; um endpoint que responde em mais de 10 segundos garante duplicatas.
- Telefone vai no formato internacional, só dígitos —
5511999998888, sem+, sem espaço, sem pontuação. Para um número que você nunca mandou mensagem, rodecheck_whatsapp_numberprimeiro e envie o id canônico que ele devolve, não o que a pessoa digitou. - Mídia já hospedada vai por URL pública
https; arquivo local vai por upload. Base64 no argumento não é aceito, e não é capricho: quando a imagem chegou ao assistente como imagem, ela não é mais uma string que ele possa recuperar — e quando os bytes existem, escrevê-los consumiria a resposta inteira (1 MB de arquivo são ~1,4 milhão de caracteres). Para um arquivo na máquina do assistente,prepare_media_uploaddevolve uma URL de upload de uso único: ele sobe o arquivo com umcurl -F 'file=@caminho'e o envio acontece nesse mesmo request, sem os bytes passarem pelo contexto. Sem um terminal disponível, a mesma URL serve para você subir o arquivo à mão. - O tipo da mídia é uma escolha, e o contato vê coisas diferentes. Uma imagem aparece na bolha com a legenda embaixo; um vídeo vira player com miniatura e toca dentro da conversa; um áudio é anexo, com botão de play e o nome do arquivo ao lado; um áudio de voz é a bolha redonda com forma de onda e velocidade 1×/1,5×/2×, que é o que as pessoas querem dizer com "manda um áudio"; um documento é o card com ícone, nome e tamanho, que o contato baixa para abrir. Tudo que não é imagem, vídeo ou áudio só pode ser documento. O assistente declara esse tipo ao pedir a URL de upload (ou no
mediatype, quando manda por URL) — ele não é adivinhado pela extensão do arquivo, justamente para um vídeo não chegar como card de arquivo. - Enviar custa dinheiro do workspace e é irreversível. As instruções do servidor pedem que o assistente confirme com você antes de enviar, a menos que você já tenha pedido aquele envio na conversa.
download_message_mediadesiste em silêncio com arquivo grande. Acima de uns 750 KB ele responde sem conteúdo e com uma nota mandando abrir o arquivo no Pingo — um sucesso, não um erro. Um cliente que só checa falha segue adiante de mãos vazias.send_whatsapp_templatepreenche só variáveis posicionais de texto. Um template comparameterFormat: NAMED, com header de mídia ou com botão dinâmico não tem como ser preenchido por esta ferramenta — esse caso é a API HTTP. A Meta recusa o envio inteiro quando nome, idioma ou número de parâmetros não batem.delayé em milissegundos e para em 20000. A espera é bloqueante do lado do WhatsApp; valores mais altos estouram a requisição por timeout depois de a mensagem ter sido aceita — erro para o assistente, mensagem entregue para o contato.
Limites
| Transporte | Streamable HTTP, stateless, só POST |
| Rate limit do endpoint MCP | 300 requisições por minuto, chaveadas pela credencial, não pelo IP |
Rate limit de /v3/oauth/token | 120 por minuto, por IP |
Rate limit de /v3/oauth/authorize e /register | 15 por minuto, por IP |
| Access token | vale 1 hora |
| Refresh token | vale 30 dias, rotacionado a cada uso |
| Código de autorização | vale 60 segundos, uso único |
| Cota de mensagens e tamanho de texto | o que o seu plano permitir — os mesmos limites que o painel aplica |
| Tamanho de mídia | por URL, o teto do próprio WhatsApp — o Pingo não tem bytes para medir e não aplica o limite do plano. Por upload (prepare_media_upload), vale o limite do seu plano por tipo de mídia |
| Exige plano pago | só set_typing_presence |
| URL de upload | uso único, vale 10 minutos |
O balde do MCP é chaveado por credencial de propósito. Um cliente MCP remoto não é chamado pelo seu navegador: quem chama é a infraestrutura que hospeda o modelo, de um punhado de IPs de saída. Por IP, um workspace derrubaria o outro.
O endpoint MCP responde só a POST. GET e DELETE devolvem 405 com Allow: POST. O servidor é stateless, então não existe sessão para um GET em fluxo receber; um cliente que abrisse um ficaria com um socket que nunca recebe mensagem.
O que ele não faz, de propósito
| Fora | Por quê |
|---|---|
| Ler ou rotacionar o segredo de assinatura de um webhook | Esse segredo é o que prova que um evento veio do Pingo. Numa ferramenta, ele vira a capacidade de forjar eventos assinados contra o seu endpoint. Rotacionar quebra a verificação em produção no instante seguinte, e nada numa conversa consegue atualizar o outro lado. |
| Criar, editar ou excluir um template de mensagem | É configuração da conexão, não mensageria, e um template recusado conta contra a qualidade da sua conta do WhatsApp Business. Ler templates continua disponível — sem isso, enviar um é impossível. |
| Qualquer coisa do helpdesk | Fora de escopo por decisão de produto. |
Os dois primeiros continuam no painel, onde há uma pessoa.
Quando algo dá errado
Uma ferramenta que falha volta como conteúdo legível, não como exceção de protocolo — então o assistente vê o motivo e consegue corrigir o rumo: pedir outro escopo, avisar você, tentar outra ferramenta. Falhas inesperadas nunca vazam detalhe; a stack e qualquer mensagem de banco ficam no log.
| Sintoma | O que significa |
|---|---|
"This integration was not granted permission to use <tool>" | Falta um escopo ao token. A recusa diz quais; reautorize o conector e aprove-os. Se repetir, verifique se o seu próprio papel no workspace tem aquela permissão — o token não excede você. |
| "This workspace has no active plan, so it cannot send messages" | Leitura continua funcionando; envio não. |
| "…reached its plan limit of N messages for the period" | É a cota, não a requisição. Repetir não resolve. |
| 403 em toda chamada, citando outro workspace | O token está preso ao workspace escolhido no consentimento. Reautorize para trocar. |
401 com error="invalid_token" | Expirado, revogado, ou emitido para outro recurso. O header WWW-Authenticate diz qual caso é e aponta o documento de metadata para começar de novo. |
| Meu webhook nunca dispara | list_webhook_deliveries responde se o Pingo chegou a tentar. Lista vazia é problema de inscrição — evento errado, ou conexão oficial. Lista com 4xx/5xx é o seu endpoint. |
Escrevendo o seu próprio cliente
Se você está escrevendo o cliente em vez de usar um pronto, quatro detalhes poupam uma tarde:
- Peça o grant
refresh_tokenno registro.grant_typesomitido significa sóauthorization_code, e aí nenhum refresh token é emitido — o seu conector para de funcionar uma hora depois e precisa de gente de novo. - Mande
token_endpoint_auth_method: "none"para ser cliente público. Omitir o campo cria um cliente confidencial, que recebe umclient_secretmostrado uma única vez e tem de enviá-lo em toda chamada de token. - A primeira requisição precisa de
Accept: application/json, text/event-stream. O transporte do MCP exige, mesmo que este servidor sempre responda JSON. - O parâmetro
resourceprende o token a um caminho. Um token emitido parahttps://api.pingonotify.com/v3/mcpé aceito sob/v3/mcpe recusado com 401 em todo o resto da API. Peça o recurso que você realmente pretende chamar.
prompt=consent força a tela mesmo quando já existe um consentimento vivo que cobre o pedido; prompt=none nunca mostra a tela e responde consent_required ou login_required. Fora disso, uma reautorização pode se completar em silêncio — mas só quando existe exatamente um consentimento que cobre os escopos pedidos e o seu papel ainda permite.
O servidor de autorização se descreve em /.well-known/oauth-authorization-server, e a metadata deste endpoint — com a lista exata dos escopos que ele usa — em /.well-known/oauth-protected-resource/v3/mcp. Os dois são cacheados por cinco minutos e não pedem credencial. Para o modelo de credenciais do resto da API, veja Autenticação.