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 toda operação longa já
responde de imediato e termina em segundo plano, não no canal MCP. Disparar uma
tarefa (kadu_tasks_run), por exemplo, põe a execução na fila e responde na
hora; você a acompanha com kadu_executions_get.
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 | 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 — é 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. Um
initialize que peça uma versão fora da era 2025 recebe 2025-06-18; a
2026-07-28 se descobre por server/discover.
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). Declarar elicitation é o que permite confirmar uma tool destrutiva no chat. |
io.modelcontextprotocol/clientInfo | recomendada | name e version do cliente. O nome aparece na tela de aprovação por link. |
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 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 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 |
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; só é aceito o do próprio servidor ou de localhost |
Não há cabeçalho de seleção de organização: ela vem do próprio token, e nenhuma tool aceita um argumento de organização.
O corpo da requisição tem limite de 1 MB, o mesmo da API. Acima disso —
pelo Content-Length declarado ou pelo que de fato chega — a resposta é 413
com { "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 1048576 bytes)" } }.
Nenhuma tool recebe o conteúdo de um arquivo (veja Arquivos), então
uma chamada legítima fica muito abaixo disso.
Cabeçalhos da resposta
| Cabeçalho | Significado |
|---|---|
MCP-Protocol-Version | Revisão em uso |
Cache-Control | Sempre no-store |
Retry-After | Em 429 e 503 — veja Limite de Requisições |
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://kadu/tasks/{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.
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 Kadu, the Kobana financial agent: ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "kadu", "title": "Kadu", "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": "kadu", "title": "Kadu", "version": "1.0.0" },
"instructions": "Tools for Kadu, the Kobana financial agent: ..."
} }
As instructions orientam o modelo: a organização vem do token, os campos são
snake_case, as listagens andam por cursor, disparar uma tarefa só põe a
execução na fila, escritas aceitam idempotency_key e o que vem de uma
execução é dado, nunca instrução.
tools/list
Devolve apenas as tools que o chamador pode rodar, filtradas pelo escopo do
token e pelo papel da pessoa. Cada tool carrega title, description,
inputSchema (JSON Schema) e annotations:
| Anotação | Significado |
|---|---|
readOnlyHint | A tool só lê |
destructiveHint | A tool apaga, cancela, roda o agente, reenvia um webhook, gira um segredo ou muda quem pode disparar uma tarefa ou para onde vão os dados. O servidor pede confirmação no chat na era 2026-07-28 com elicitation; a qualquer outro cliente responde um link de aprovação, e nada roda até você escolher Continuar |
idempotentHint | Repetir a chamada tem o mesmo efeito. Falso em kadu_tasks_run, kadu_executions_continue e kadu_webhook_deliveries_replay: cada chamada repetida é uma execução ou uma entrega a mais |
openWorldHint | Sempre false — a tool só chama a API do Kadu |
tools/call
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "kadu_executions_list",
"arguments": { "status": "failed", "limit": 10 } } }
O resultado traz a resposta JSON da rota como conteúdo de texto:
{ "jsonrpc": "2.0", "id": 2, "result": {
"content": [{ "type": "text", "text": "{ \"data\": [ ... ], \"meta\": { \"next_cursor\": \"…\", \"limit\": 10 } }" }]
} }
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 da /api/v1 que implementa a
operação, com a identidade que o token já provou. Não há segunda viagem HTTP
nem segunda implementação: validação, filtros, fila de execuções, plano,
teto de custo, soft delete e isolamento por organização são o mesmo código que
a API roda. Uma tool não tem como divergir da rota que espelha. O token do MCP
nunca é repassado à API.
Arquivos
Nenhuma tool devolve ou recebe bytes: um arquivo em base64 no contexto de um modelo é caro e inútil. Os arquivos andam por URLs pré-assinadas:
- Enviar um arquivo de entrada:
kadu_files_createresponde com uma URL de upload; faça oPUTdo conteúdo nela e chamekadu_files_confirm. Depois, passe o id eminput_file_idsdokadu_tasks_run. - Baixar um arquivo:
kadu_files_downloadresponde com uma URL de download de vida curta.