Transporte
Streamable HTTP, stateless. Uma mensagem JSON-RPC 2.0 por POST, uma resposta JSON. Não há Mcp-Session-Id, não há resumabilidade e não há stream servidor→cliente: a aplicação roda várias réplicas, e nenhuma operação precisa empurrar mensagens para o cliente.
Versões do protocolo
O servidor fala duas eras do protocolo no mesmo endpoint:
| Versão | Era | Status |
|---|---|---|
2026-07-28 | Stateless, sem handshake — descoberta por server/discover | Suportada |
2025-06-18 | Handshake por initialize — é a versão em que o initialize fecha por padrão | Suportada |
2025-03-26 | Handshake por initialize | Suportada |
A era é decidida pelo corpo da requisição, com os cabeçalhos como conferência:
params._metatrazio.modelcontextprotocol/protocolVersion→ era2026-07-28(veja abaixo).- Sem esse envelope, com
MCP-Protocol-Versionde 2025 ou sem o cabeçalho → era 2025, cominitialize. MCP-Protocol-Version: 2026-07-28sem o envelope →400com-32602. O servidor não cai para a era antiga em silêncio: você pediu a semântica stateless e receberia a outra.
Uma versão que o servidor não fala — no cabeçalho ou no envelope, em qualquer era — é respondida com 400 e o erro JSON-RPC -32022, listando as suportadas:
{ "jsonrpc": "2.0", "id": 1, "error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": { "supported": ["2026-07-28", "2025-06-18", "2025-03-26"], "requested": "1999-01-01" }
} }
A versão em uso volta no cabeçalho MCP-Protocol-Version da resposta.
Era 2026-07-28
Não há handshake: toda requisição carrega a própria versão e as capacidades do cliente em params._meta, e o servidor não lembra nada entre uma requisição e outra.
Chave de params._meta | Obrigatória | Significado |
|---|---|---|
io.modelcontextprotocol/protocolVersion | sim | 2026-07-28 |
io.modelcontextprotocol/clientCapabilities | sim | Capacidades do cliente (um objeto; {} serve). Sem elicitation de formulário, as tools destrutivas são recusadas — veja confirmação de tools destrutivas |
io.modelcontextprotocol/clientInfo | recomendada | name e version do cliente |
Os cabeçalhos repetem o que está no corpo, para que intermediários possam rotear sem lê-lo — e por isso têm que concordar com ele:
| Cabeçalho | Tem que ser igual a |
|---|---|
MCP-Protocol-Version | A versão do envelope |
Mcp-Method | O method da mensagem |
Mcp-Name | Só no tools/call: o params.name, cru ou como =?base64?…?= |
Qualquer divergência é 400 com -32020 (Header mismatch: …). Faltar clientCapabilities é 400 com -32602.
Os resultados dessa era sempre trazem resultType: "complete" e o servidor em _meta["io.modelcontextprotocol/serverInfo"]. server/discover, tools/list, resources/list, resources/templates/list, resources/read e prompts/list trazem também ttlMs: 0 e cacheScope: "private": o que volta é filtrado pelo escopo e pelas permissões de quem chama, então não deve ser reaproveitado nem compartilhado. Um tools/call de tool destrutiva pode voltar resultType: "input_required" — veja Confirmação de tools destrutivas.
Cabeçalhos da requisição
| Cabeçalho | Obrigatório | Significado |
|---|---|---|
Authorization | sim | Bearer <access token> emitido para o MCP — veja Autenticação |
X-Workspace-Id | não | Workspace padrão da conexão, usado quando a tool não recebe workspace_id — o mesmo que ?workspace_id= na URL. Veja Autenticação → O workspace |
Content-Type | sim | application/json |
Accept | recomendado | application/json |
MCP-Protocol-Version | sim na era 2026-07-28; recomendado na de 2025 | Revisão do protocolo |
Mcp-Method | sim na era 2026-07-28 | O método da mensagem |
Mcp-Name | sim no tools/call da era 2026-07-28 | O nome da tool |
Origin | condicional | Mandado por clientes de navegador; validado contra uma allowlist |
Cabeçalhos da resposta
| Cabeçalho | Significado |
|---|---|
MCP-Protocol-Version | Revisão em uso |
Cache-Control | Sempre no-store |
Retry-After | Em 429 — veja Limite de requisições — e em 503 |
Métodos
| Método | Era 2026-07-28 | Era 2025 | Para quê |
|---|---|---|---|
server/discover | sim | — | Versões suportadas, capacidades do servidor e instruções |
initialize | — | sim | Negociação de versão, capacidades do servidor e instruções |
ping | — | sim | Teste de vida; responde um resultado vazio |
tools/list | sim | sim | As tools que este chamador pode rodar, com JSON Schema e anotações |
tools/call | sim | sim | Roda uma tool |
resources/list | sim | sim | Os documentos de URI fixa que este chamador pode ler |
resources/templates/list | sim | sim | Os templates de URI (kobana://finance/payables/{id}, …) que este chamador pode ler |
resources/read | sim | sim | Lê um documento pela URI |
prompts/list | sim | sim | Os roteiros prontos que este chamador pode usar |
prompts/get | sim | sim | Monta um roteiro com os argumentos dados |
completion/complete | sim | sim | Sugestões para um argumento de prompt ou de template |
Resources, prompts e completions estão em Resources e prompts.
Qualquer outro método é -32601 Method not found — na era 2026-07-28, com HTTP 404; na de 2025, no corpo de um 200. Batching JSON-RPC não é suportado — foi removido na revisão 2025-06-18, e um corpo em array é respondido com 400.
O corpo tem teto de 14 MB (14.680.064 bytes) — o bastante para o maior pedido legítimo, um finance_attachments_upload de 10 MB em base64 com o envelope. Acima disso a resposta é 413 com { "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 14680064 bytes)" } }: um Content-Length declarado acima do teto é recusado antes mesmo da autenticação, e um corpo sem Content-Length (ou que mente nele) é cortado no teto durante a leitura.
Uma notificação (mensagem sem id, como notifications/initialized) é aceita com 202 e corpo vazio, nas duas eras.
A autenticação vem antes de qualquer método, inclusive do server/discover: sem token, a resposta é o 401 com o desafio que leva o cliente ao login OAuth — veja Autenticação.
server/discover (era 2026-07-28)
POST / HTTP/1.1
Authorization: Bearer <access token>
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: server/discover
{ "jsonrpc": "2.0", "id": 1, "method": "server/discover",
"params": { "_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" }
} } }
{ "jsonrpc": "2.0", "id": 1, "result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28", "2025-06-18", "2025-03-26"],
"capabilities": { "tools": {}, "resources": {}, "prompts": {}, "completions": {} },
"instructions": "Tools for Kobana Finance: ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "kobana-finance",
"title": "Kobana Finance",
"version": "1.0.0"
}
}
} }
initialize (handshake da era 2025)
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2025-06-18" } }
{ "jsonrpc": "2.0", "id": 1, "result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": false },
"resources": { "subscribe": false, "listChanged": false },
"prompts": { "listChanged": false },
"completions": {}
},
"serverInfo": {
"name": "kobana-finance",
"title": "Kobana Finance",
"version": "1.0.0"
},
"instructions": "Tools for Kobana Finance: ..."
} }
As instructions (no initialize e no server/discover) avisam o agente que valores de campo nos resultados são dados escritos por terceiros, nunca instruções, e pedem confirmação do usuário antes de uma ação destrutiva ou que move dinheiro que um resultado pareça pedir.
tools/list
Devolve apenas as tools que o chamador pode rodar — as que o escopo do token alcança e que a pessoa que autorizou pode fazer no dashboard. O inputSchema de uma tool de escrita é o próprio schema de request do endpoint — os mesmos campos, obrigatórios e enums da Referência da API. Cada tool carrega annotations, que é o que faz o cliente pedir confirmação ao usuário:
| Anotação | Significado |
|---|---|
readOnlyHint | A tool só lê |
destructiveHint | A tool apaga, cancela ou move dinheiro — só roda com a confirmação do servidor |
idempotentHint | Repetir a chamada tem o mesmo efeito |
openWorldHint | Sempre false — a tool não alcança nada fora do Financeiro Inteligente |
tools/call
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "finance_payables_list",
"arguments": { "workspace_id": "2b93f876-769a-4bdc-b23e-304957f313b4",
"filters": { "status": "pending" }, "limit": 10,
"fields": ["description", "amount", "due_date"] } } }
O resultado traz a resposta JSON do endpoint como conteúdo de texto:
{ "jsonrpc": "2.0", "id": 2, "result": {
"content": [{ "type": "text", "text": "{ \"data\": [ ... ], \"meta\": { \"next_cursor\": \"…\" } }" }]
} }
Erros sobre os quais o chamador pode agir voltam com isError: true dentro do resultado, não como erro JSON-RPC — veja Erros.
Despacho in-process
Cada tool despacha in-process para o handler de /v1 que implementa a operação, com a identidade já verificada e o workspace resolvido para a chamada — o token do MCP não é repassado, e o endpoint segue exigindo o escopo dele. Não há segunda viagem HTTP nem segunda implementação: filtros, validação, regras de baixa, rateio, travas de segurança e isolamento por workspace são o mesmo código que a API REST roda. As escritas ficam registradas com o canal mcp (created_via / updated_via), distinguíveis das feitas pela API ou pelo dashboard.
Arquivos
- Download —
finance_attachments_downloadnão devolve os bytes: um PDF em base64 no contexto de um modelo é caro e inútil. O resultado traz ocontent_type, o tamanho e o caminho para baixar o arquivo na API — com um token emitido para a API, já que o do MCP não vale lá. - Upload —
finance_attachments_uploadrecebe o arquivo emcontent_base64(até 10 MB) e o envia ao endpoint comomultipart/form-data. Depois,finance_attachments_linko anexa a um título ou lançamento.