Tudo o que um cliente MCP faz, um script pode fazer com HTTPS comum. Um endpoint, uma chave de API, JSON na entrada e JSON na saída. Este guia cobre autenticação, as três chamadas de que você precisa, erros, novas tentativas, limites e webhooks para e-mails novos.
Toda requisição é um POST JSON-RPC 2.0 para https://mcpemails.com/api/mcp. É o mesmo endpoint que o claude.ai, o ChatGPT e outros clientes MCP usam, com as mesmas ferramentas e as mesmas permissões, então um script e um assistente de chat enxergam um único produto.
Content-Type: application/json e Accept: application/json. A resposta é um único objeto JSON.Authorization.structuredContent, então você nunca precisa interpretar texto corrido. Os mesmos dados estão em content[0].text como uma string JSON.mcpe_ seguido de 64 caracteres hexadecimais. Ela aparece uma única vez, quando você a cria, e só um hash fica armazenado. Copie-a nesse momento; uma chave perdida não pode ser recuperada, só substituída.Authorization: Bearer mcpe_.... Nunca coloque uma chave em uma URL ou em uma query string.Cada chave tem só as permissões que você marca ao criá-la. Você pode alterá-las depois em Editar acesso, e a mudança vale a partir da próxima chamada, sem reconectar.
| Escopo | Permite |
|---|---|
read:email | Listar, ler e pesquisar mensagens, baixar anexos, listar caixas de entrada e pastas. |
search:email | Mantido para chaves antigas. A pesquisa já está coberta por read:email. |
send:email | Enviar, responder e encaminhar. |
manage:drafts | Criar, atualizar e excluir rascunhos. Enviar um rascunho também exige send:email. |
manage:folders | Mover, copiar, sinalizar e arquivar mensagens; criar, renomear e excluir pastas. |
delete:email | Excluir mensagens, uma a uma ou em massa. |
manage:contacts | Pesquisar contatos nos seus e-mails recentes. |
schedule:email | Agendar envios e gerenciar e-mails agendados. |
manage:automations | Criar regras que rodam em um horário programado sem ninguém acompanhando. |
Comece com read:email e adicione um escopo de escrita só quando o fluxo de trabalho precisar. Uma chamada fora dos escopos da chave é recusada antes de entrarmos em contato com o seu servidor de e-mail. Visualizadores de um espaço de trabalho só podem ter escopos de leitura.
Por padrão, uma chave alcança todas as caixas de entrada do espaço de trabalho. Para restringi-la a algumas delas, abra Editar acesso na chave e escolha as caixas em Acesso às caixas de entrada. O limite é verificado em toda chamada, então uma chave restrita à caixa de suporte não consegue ler a do financeiro, diga o que disser a requisição.
Apps que as próprias pessoas conectam, como o claude.ai, o ChatGPT e outros clientes MCP, entram com OAuth em vez de uma chave: registro dinâmico de cliente, código de autorização com PKCE e uma tela de consentimento onde o usuário escolhe os escopos e as caixas de entrada. Veja Conexão OAuth. Para os seus próprios scripts, servidores e fluxos de trabalho, uma chave de API é mais simples.
Clientes MCP começam com initialize. Um script pode pular essa etapa, porque o servidor não mantém sessão, mas ela é uma forma barata de conferir se a chave funciona e de ler as instruções do servidor.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"clientInfo": { "name": "my-agent", "version": "1.0" },
"capabilities": {}
}
}'tools/list retorna todas as ferramentas que essa chave pode chamar, com a descrição, o schema de entrada e o schema de saída de cada uma. Leia a lista na inicialização em vez de fixar nomes de ferramentas ou argumentos no código: ela é filtrada pelos escopos da chave, e as ferramentas ganham argumentos com o tempo.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'Chame uma ferramenta pelo nome com seus argumentos. A maioria das ferramentas recebe uma action que define o que ela faz, como list, read ou search em email_read. Indique a caixa de correio com inbox (o endereço) ou inbox_id; inbox_list retorna os dois.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "email_read",
"arguments": { "action": "list", "inbox": "you@example.com", "unread": true, "limit": 20 }
}
}'Um resultado bem-sucedido:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{ "type": "text", "text": "{\"messages\":[...],\"total\":42, ...}" }],
"structuredContent": {
"messages": [{ "id": "...", "subject": "...", "from": { "name": "...", "email": "..." } }],
"total": 42,
"total_is_estimate": false,
"has_more": true,
"next_offset": 20
}
}
}A referência de ferramentas descreve cada ferramenta, e o guia de curl tem mais exemplos.
Há dois tipos. Uma requisição que nem chegou a rodar volta como um error JSON-RPC com um status HTTP. Uma requisição que chegou a uma ferramenta e foi recusada por ela volta como um result normal com isError: true, para que um modelo de IA leia o motivo. Tome decisões com base em data.error_code ou no código em _meta, não no texto da mensagem.
| Resposta | Quando | O que fazer |
|---|---|---|
HTTP 401, -32001 | Chave ausente, malformada, revogada ou expirada. | Corrija o cabeçalho Authorization ou crie uma chave nova. Tentar de novo não resolve. |
HTTP 403, -32004, insufficient_scope | Falta à chave um escopo de que essa ação da ferramenta precisa. data.required_scopes lista o que a permitiria. | Adicione o escopo em Editar acesso ou use outra chave. |
HTTP 429, -32003, rate_limit_exceeded | Requisições demais. data.window diz qual janela encheu. | Espere os segundos de Retry-After (também em data.retry_after) e repita a mesma requisição. |
-32602 | Nome de ferramenta desconhecido. data.available_tools lista os nomes que essa chave pode chamar. | Use um nome de tools/list. |
isError: true, invalid_arguments | Os argumentos não passaram no schema da ferramenta. Nada foi executado. O texto aponta cada argumento inválido e, quando o nome de um argumento foi chutado, indica o que existe. | Corrija os argumentos. Os mesmos argumentos são rejeitados do mesmo jeito. |
isError: true, usage_limit_reached | Um espaço de trabalho Free usou suas ações de e-mail do mês. | Espere a data de renovação em _meta ou faça upgrade. Os planos pagos têm ações de e-mail ilimitadas. |
isError: true, outro | A ferramenta rodou e falhou: mensagem não encontrada, provedor recusou, destinatário inválido e assim por diante. | Leia content[0].text. Ele diz o que aconteceu e se algo foi enviado. |
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", ...
{
"jsonrpc": "2.0",
"id": 4,
"error": {
"code": -32004,
"message": "Insufficient scope: the 'send:email' scope is required to call email_compose.",
"data": { "error_code": "insufficient_scope", "required_scopes": ["send:email"], ... }
}
}HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32003,
"message": "Rate limit exceeded",
"data": { "error_code": "rate_limit_exceeded", "window": "per_minute", "retry_after": 12, ... }
}
}{
"jsonrpc": "2.0",
"id": 6,
"result": {
"isError": true,
"content": [{ "type": "text", "text": "Invalid arguments for email_read. The call was rejected before it ran, ..." }],
"_meta": {
"com.mcpemails/invalid_arguments": {
"error_code": "invalid_arguments",
"retryable": false,
"errors": [{ "path": "arguments.since", "message": "..." }]
}
}
}
}Todo resultado de listagem e de pesquisa termina com os mesmos quatro campos:
has_more: true quando existe outra página. Só has_more: false significa que você viu tudo.next_offset: devolva-o como offset, com todos os outros argumentos iguais, para obter a próxima página. É null quando has_more é false.total: o número de resultados, ou null quando o provedor não consegue contá-los. Nunca contradiz a página que você tem em mãos.total_is_estimate: true quando total é uma estimativa ou um valor mínimo, e não uma contagem exata.Corpos de mensagem longos são divididos em janelas da mesma forma: quando body_truncated é true, leia de novo passando body_next_offset como body_offset.
Uma conexão que cai depois de um envio deixa você sem saber se o e-mail saiu. Passe uma idempotency_key e tente de novo com a mesma chave: o trabalho é executado uma única vez.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "email_compose",
"arguments": {
"action": "send",
"inbox": "you@example.com",
"to": ["customer@example.com"],
"subject": "Your order has shipped",
"body": "Hi, your order is on its way.",
"idempotency_key": "order-1042-shipped"
}
}
}'tools/list mostra idempotency_key no schema de entrada de toda ferramenta que a aceita.idempotent_replay: true, o status original e o resultado original em result, incluindo o message_id que você pode ter perdido.idempotency_key_conflict. Use uma chave nova para uma operação nova.status: "processing". Tente de novo em instantes.status: "unknown". Confira os Enviados antes de enviar de novo com uma chave nova.{
"idempotency_key": "order-1042-shipped",
"idempotent_replay": true,
"status": "succeeded",
"result": { "message_id": "...", "inbox_id": "...", "status": "sent" },
"message": "This logical request was already processed. ..."
}Qualquer caixa de entrada pode segurar todos os envios até que uma pessoa os aprove. Um envio do seu script passa então a retornar um approval_id com status: "pending_approval" em vez de sair, e ele sai quando alguém o aprova no painel. Isso funciona em todos os planos e é um bom padrão enquanto uma automação nova ainda está provando seu valor. Uma nova tentativa com a mesma idempotency_key informa a aprovação existente em vez de criar uma segunda.
Cada plano tem um teto por minuto para o espaço de trabalho inteiro. Uma única chave pode usar todo o limite por minuto do plano; várias chaves o compartilham.
| Plano | Requisições por minuto | Webhooks |
|---|---|---|
| Free | 60 | Não |
| Personal | 120 | Não |
| Pro | 300 | Sim |
| Business | 1.000 | Sim |
tools/call trazem RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset (em segundos) para a janela que estiver mais perto de encher, para que o cliente possa regular o ritmo antes de atingir o limite.data.window a janela que encheu.Retry-After em segundos. Espere esse tempo e repita a mesma requisição.No Pro e no Business, o MCPEmails pode fazer um POST na sua URL quando chega e-mail em uma caixa de entrada, para que um fluxo de trabalho comece com o e-mail novo em vez de fazer polling. Crie um endpoint no painel, escolha as caixas de entrada dele e copie o segredo de assinatura, que aparece uma única vez. A URL precisa ser HTTPS, na porta 443 ou 8443. Use o botão de teste para enviar um evento ping assinado.
POST /your/webhook HTTP/1.1
Content-Type: application/json
MCPEmails-Event-Id: evt_...
MCPEmails-Event-Type: message.received
MCPEmails-Signature: t=1792843200,v1=5f2b...
{
"id": "evt_...",
"type": "message.received",
"created_at": "2026-10-24T09:20:00.000Z",
"workspace_id": "...",
"inbox_id": "...",
"folder": "INBOX",
"count": 1,
"message_ids": ["..."]
}type é message.received. message_ids lista as mensagens novas na pasta INBOX daquela caixa de entrada, até 10 por evento; count é quantas chegaram, o que pode ser mais.messages com o id, o from e o subject de cada mensagem.email_read (action: "read", o inbox_id e um message_id) usando uma chave de API. Os escopos e o acesso às caixas de entrada dessa chave valem normalmente.Cada entrega traz MCPEmails-Signature: t=<unix seconds>,v1=<hex>, em que v1 é o HMAC-SHA256 do timestamp, um ponto e o corpo bruto da requisição, com o segredo do endpoint como chave. A chave é o segredo inteiro como aparece, prefixo whsec_ incluído. Recalcule a assinatura sobre o corpo bruto, compare em tempo constante e rejeite um timestamp com mais de cinco minutos.
import crypto from 'node:crypto';
// rawBody: the request body exactly as received, before JSON.parse.
export function verifyMcpemailsSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
String(header ?? '').split(',').map((part) => part.trim().split('=')),
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}MCPEmails-Event-Id para ignorar uma entrega que você já processou. Uma nova tentativa traz o mesmo id de evento.POST e registre a URL de produção dele como endpoint de webhook no painel do MCPEmails.POST, URL https://mcpemails.com/api/mcp, autenticação Generic Credential Type → Header Auth com nome Authorization e valor Bearer mcpe_....result.structuredContent.NODE_FUNCTION_ALLOW_BUILTIN=crypto.{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "email_read",
"arguments": {
"action": "read",
"inbox_id": "{{ $json.body.inbox_id }}",
"message_id": "{{ $json.body.message_ids[0] }}"
}
}
}Para um nó AI Agent que lê e envia e-mails por conta própria, veja o guia do n8n.
Adicione o servidor com uma chave em um cabeçalho. Isso serve para máquinas sem interface e para CI, onde não há navegador para o login OAuth:
claude mcp add --transport http mcpemails https://mcpemails.com/api/mcp \
--header "Authorization: Bearer $MCPEMAILS_KEY"O guia do Claude Code cobre o login OAuth e a escolha de escopos.
Lista todas as mensagens não lidas de uma caixa de entrada, página por página, e espera passar um 429:
// node agent.mjs (Node 18 or later, MCPEMAILS_KEY in the environment)
const ENDPOINT = 'https://mcpemails.com/api/mcp';
let nextId = 1;
async function callTool(name, args) {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MCPEMAILS_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ jsonrpc: '2.0', id: nextId++, method: 'tools/call', params: { name, arguments: args } }),
});
if (res.status === 429) {
const wait = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return callTool(name, args);
}
const reply = await res.json();
if (reply.error) throw new Error(`${reply.error.code}: ${reply.error.message}`);
if (reply.result.isError) throw new Error(reply.result.content[0].text);
return reply.result.structuredContent;
}
const inbox = 'you@example.com';
let offset = 0;
do {
const page = await callTool('email_read', { action: 'list', inbox, unread: true, limit: 50, offset });
for (const m of page.messages) console.log(m.id, m.from?.email, m.subject);
offset = page.has_more ? page.next_offset : null;
} while (offset !== null);O OpenClaw entra com OAuth por padrão; o guia do OpenClaw mostra o passo a passo. Para usar uma chave com escopos, coloque-a no mapa headers da entrada do servidor e deixe auth sem valor, porque o OpenClaw ignora um cabeçalho Authorization estático em uma entrada configurada para OAuth.
{
"mcp": {
"servers": {
"mcpemails": {
"url": "https://mcpemails.com/api/mcp",
"transport": "streamable-http",
"headers": { "Authorization": "Bearer mcpe_YOUR_KEY_HERE" }
}
}
}
}Crie uma chave, chame tools/list e veja seus próprios e-mails voltarem como JSON.