MCP (assistentes de IA)
A Notavia expõe um servidor MCP (Model Context Protocol) remoto, para que assistentes de IA operem a conta de um usuário. É um servidor OAuth 2.1 + MCP sobre Streamable HTTP, sem sessão (stateless). Para guia de uso por quem não é desenvolvedor, veja a Central de Ajuda: Assistentes de IA.
Estado: em liberação gradual. Exige que a Notavia habilite a funcionalidade
mcp-clientespara a conta e plano pago. A API de integração porX-Api-Key(restante desta documentação) não é o MCP e continua como está.
Visão geral
| Item | Valor |
|---|---|
| Servidor MCP (recurso) | https://mcp.notavia.com.br/mcp |
| Servidor de autorização (issuer) | https://app.notavia.com.br |
| Transporte | Streamable HTTP, POST /mcp (JSON-RPC 2.0), sem Mcp-Session-Id |
| Autenticação | Authorization: Bearer <access_token> em toda requisição |
| Limite | 120 requisições por minuto por token (excedido: 429) |
O servidor de autorização fica no host do portal porque a tela de consentimento usa a sessão de login do portal; o recurso fica em outro host e o descobre por metadados (RFC 9728).
Descoberta
Uma requisição sem token (ou com token inválido) recebe 401 com o ponteiro dos metadados:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.notavia.com.br/.well-known/oauth-protected-resource", scope="notavia.read"
| Endpoint | Padrão | Conteúdo |
|---|---|---|
GET https://mcp.notavia.com.br/.well-known/oauth-protected-resource |
RFC 9728 | resource, authorization_servers, scopes_supported |
GET https://app.notavia.com.br/.well-known/oauth-authorization-server |
RFC 8414 | endpoints, code_challenge_methods_supported, registration_endpoint, client_id_metadata_document_supported |
GET https://app.notavia.com.br/.well-known/openid-configuration |
OIDC Discovery | os mesmos metadados |
curl -s https://mcp.notavia.com.br/.well-known/oauth-protected-resource
curl -s https://app.notavia.com.br/.well-known/oauth-authorization-server
O resource do documento é idêntico à URL do MCP (https://mcp.notavia.com.br/mcp).
Endpoints OAuth (host do portal)
| Endpoint | Uso |
|---|---|
GET/POST /connect/authorize |
Autorização (login + tela de consentimento). Exige PKCE S256 |
POST /connect/token |
Troca do código e renovação (application/x-www-form-urlencoded) |
POST /connect/register |
Registro dinâmico de cliente (DCR, RFC 7591), JSON |
Regras do fluxo:
- Authorization code + PKCE (
S256) obrigatório. Semcode_challenge, a autorização nem começa. - O parâmetro
resource(RFC 8707), quando enviado, deve serhttps://mcp.notavia.com.br/mcp; qualquer outro valor é recusado (invalid_target). O token sai com essa audiência. - Access token: opaco, validade de 1 hora. Refresh token: validade de 30 dias, com rotação (cada renovação devolve um refresh novo e invalida o anterior; reutilizar o antigo é recusado).
- O código de autorização vale 5 minutos e uma única vez.
- A renovação reavalia a conta: suspensa, no plano Free ou sem a funcionalidade →
invalid_grant. offline_accessé aceito para obter o refresh token.
Registro do cliente
Há duas formas, as duas aceitas pelos assistentes atuais.
CIMD — Client ID Metadata Document (preferido)
O client_id é uma URL https (com caminho, sem fragmento, porta ou credenciais) que serve um documento JSON:
{
"client_id": "https://app.exemplo.com/oauth/client.json",
"client_name": "Meu Assistente",
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"token_endpoint_auth_method": "none"
}
O servidor busca o documento (sem seguir redirecionamentos, até 5 s e 64 KB) e exige que client_id seja
idêntico à URL buscada. URLs que apontam para a rede interna (IPs privados, localhost, link-local) são
recusadas.
DCR — registro dinâmico
curl -s -X POST https://app.notavia.com.br/connect/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Meu Assistente",
"redirect_uris": ["http://localhost/callback"],
"token_endpoint_auth_method": "none"
}'
{
"client_id": "dcr_0f3a9c…",
"client_name": "Meu Assistente",
"redirect_uris": ["http://localhost/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "notavia.read notavia.emitir notavia.cancelar"
}
O registro é idempotente: o mesmo nome com os mesmos redirect_uris devolve sempre o mesmo client_id.
Só clientes públicos (token_endpoint_auth_method: none) são aceitos.
redirect_uri permitidos
O redirect_uri só pode ser de um assistente conhecido ou de loopback — qualquer outro valor é recusado no
registro (proteção contra desvio do código de autorização):
| Origem | Valores aceitos |
|---|---|
| Claude (claude.ai, Desktop, mobile) | https://claude.ai/api/mcp/auth_callback (e https://claude.com/api/mcp/auth_callback) |
| ChatGPT | https://chatgpt.com/connector_platform_oauth_redirect e https://chatgpt.com/connector/oauth/{callback_id} |
| OpenAI (portal de apps) | https://platform.openai.com/apps-manage/oauth… |
| Aplicativos locais (Claude Code, Gemini CLI) | http://localhost, http://127.0.0.1 ou http://[::1], qualquer porta e caminho (a porta é ignorada na comparação, RFC 8252) |
Sem fragmento, sem credenciais embutidas, https na porta padrão. Precisa de outro assistente? Fale com o
suporte.
Escopos
| Escopo | Permite |
|---|---|
notavia.read |
Consultar notas, cadastros, cota e simulador de impostos. Sempre concedido |
notavia.emitir |
Emitir documentos fiscais em nome da empresa |
notavia.cancelar |
Cancelar documentos fiscais |
notavia.emitir e notavia.cancelar só entram no token quando (1) o Admin da conta permitiu emissão por
assistentes de IA, (2) o cliente pediu o escopo e (3) o usuário o marcou na tela de consentimento. Um
escopo concedido deixa de valer na próxima chamada se a conta retirar a permissão.
Exemplo de fluxo (curl)
# 1) PKCE
VERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=\n')
# 2) Abra no navegador (login + consentimento) e copie o "code" do redirecionamento:
echo "https://app.notavia.com.br/connect/authorize?response_type=code&client_id=$CLIENT_ID\
&redirect_uri=http%3A%2F%2Flocalhost%3A53124%2Fcallback&scope=notavia.read%20offline_access\
&state=xyz&code_challenge=$CHALLENGE&code_challenge_method=S256\
&resource=https%3A%2F%2Fmcp.notavia.com.br%2Fmcp"
# 3) Troca do código
curl -s -X POST https://app.notavia.com.br/connect/token \
-d grant_type=authorization_code -d code="$CODE" \
-d redirect_uri="http://localhost:53124/callback" -d client_id="$CLIENT_ID" \
-d code_verifier="$VERIFIER" -d resource="https://mcp.notavia.com.br/mcp"
# 4) Renovação (o refresh anterior deixa de valer)
curl -s -X POST https://app.notavia.com.br/connect/token \
-d grant_type=refresh_token -d refresh_token="$REFRESH" -d client_id="$CLIENT_ID" \
-d resource="https://mcp.notavia.com.br/mcp"
Chamando o MCP
O transporte responde JSON direto ou um evento SSE (data: {...}); envie Accept: application/json, text/event-stream.
# Listar as ferramentas
curl -s https://mcp.notavia.com.br/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Chamar uma ferramenta
curl -s https://mcp.notavia.com.br/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"minha_conta","arguments":{}}}'
Ferramentas
| Ferramenta | Escopo | Estado |
|---|---|---|
minha_conta |
notavia.read |
Disponível |
| Consulta de notas, cadastros, simulador e tabelas fiscais | notavia.read |
Em desenvolvimento |
| Preparar e emitir / cancelar (em dois passos) | notavia.emitir / notavia.cancelar |
Em desenvolvimento |
Toda ferramenta traz title e as anotações readOnlyHint / destructiveHint / idempotentHint.
Ferramentas de escrita só aparecem em tools/list quando o token as cobre e a empresa permite. Emitir e
cancelar são sempre em dois passos: preparar_* devolve uma prévia e um token de confirmação vinculado ao
conteúdo exato; emitir_*/cancelar_* só executam com esse token, sem alteração. A emissão usa o mesmo
pipeline assíncrono da API de integração (jobId), com idempotência.
Tokens de acesso pessoal (planejado)
Clientes que integram por código e não conseguem fazer o login no navegador (por exemplo a API do Grok ou
SDKs) usarão um token de acesso pessoal: criado pelo Admin na área de Administração, com escopos limitados,
validade obrigatória, exibido uma única vez (só o hash fica gravado) e revogável na hora. Vai no mesmo
cabeçalho Authorization: Bearer. Não disponível ainda.
Erros
| HTTP | Quando |
|---|---|
401 |
Sem token, token inválido, expirado ou revogado (WWW-Authenticate com resource_metadata) |
402 |
Conta no plano Free (recurso só em planos pagos) |
403 |
Conta suspensa/encerrada ou usuário sem acesso; ou escopo não concedido |
404 |
Recurso não habilitado para a conta |
429 |
Mais de 120 requisições por minuto no mesmo token |
Erros de ferramenta (parâmetro inválido, escopo ausente) voltam como resultado MCP com isError: true.
Segurança em resumo
PKCE S256; redirect_uri por lista; refresh com rotação; tokens opacos revogáveis na hora; audiência do token
validada; a conta, o papel e a restrição por CNPJ são relidos do banco a cada chamada; nenhum certificado, chave
de API ou segredo sai em ferramenta alguma. Dados vindos de notas e cadastros chegam como dado, nunca como
instrução — implemente o seu cliente da mesma forma.