Saltar al contenido principal

Transporte

Streamable HTTP, stateless. Un mensaje JSON-RPC 2.0 por POST, una respuesta JSON. No hay Mcp-Session-Id, no hay reanudación y no hay stream servidor→cliente: la aplicación corre varias réplicas, y toda operación larga ya responde de inmediato y termina en un worker en segundo plano, no en el canal MCP.

Versiones del protocolo​

El servidor habla dos eras del protocolo en el mismo endpoint:

VersiónEraEstado
2026-07-28Stateless, sin handshake — se descubre con server/discoverSoportada
2025-06-18Handshake con initialize — la versión en la que initialize cierra por defectoSoportada
2025-03-26Handshake con initializeSoportada

La era se decide por el cuerpo de la petición, con las cabeceras como verificación:

  • params._meta trae io.modelcontextprotocol/protocolVersion → era 2026-07-28 (ve abajo).
  • Sin ese sobre, con MCP-Protocol-Version de 2025 o sin la cabecera → era 2025, con initialize.
  • MCP-Protocol-Version: 2026-07-28 sin el sobre → 400 con -32602. El servidor no cae a la era antigua en silencio: pediste la semántica stateless y recibirías la otra.

Una versión que el servidor no habla — en la cabecera o en el sobre, en cualquier era — se responde con 400 y el error JSON-RPC -32022, listando las soportadas:

{ "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" }
} }

La versión en uso vuelve en la cabecera MCP-Protocol-Version de la respuesta.

Era 2026-07-28​

No hay handshake: cada petición lleva su propia versión y las capacidades del cliente en params._meta, y el servidor no recuerda nada de una petición a otra.

Clave de params._metaObligatoriaSignificado
io.modelcontextprotocol/protocolVersionsí2026-07-28
io.modelcontextprotocol/clientCapabilitiessíCapacidades del cliente (un objeto; {} sirve)
io.modelcontextprotocol/clientInforecomendadaname y version del cliente

Las cabeceras repiten lo que dice el cuerpo, para que los intermediarios puedan enrutar sin leerlo — y por eso tienen que coincidir con él:

CabeceraTiene que ser igual a
MCP-Protocol-VersionLa versión del sobre
Mcp-MethodEl method del mensaje
Mcp-NameSolo en tools/call: el params.name, crudo o como =?base64?…?=

Cualquier discrepancia es 400 con -32020 (Header mismatch: …). Si falta clientCapabilities, es 400 con -32602.

Los resultados de esta era siempre traen resultType: "complete" y el servidor en _meta["io.modelcontextprotocol/serverInfo"]. server/discover, tools/list, resources/list, resources/templates/list, resources/read y prompts/list traen además ttlMs: 0 y cacheScope: "private": lo que vuelve se filtra por los permisos de quien llama, así que no debe reutilizarse ni compartirse. Un tools/call de una tool destructiva puede volver con resultType: "input_required" — ve Confirmación de tools destructivas.

Cabeceras de la petición​

CabeceraObligatoriaSignificado
AuthorizationsíBearer <access token> emitido para el MCP — ve Autenticación
Content-Typesíapplication/json
Acceptrecomendadaapplication/json
MCP-Protocol-Versionsí en la era 2026-07-28; recomendada en la de 2025Revisión del protocolo
Mcp-Methodsí en la era 2026-07-28El método del mensaje
Mcp-Namesí en tools/call de la era 2026-07-28El nombre de la tool
OrigincondicionalEnviada por clientes de navegador; validada contra una allowlist

No hay cabecera de selección de inquilino: la organización viene del propio token, y ninguna tool acepta un argumento de organización.

El cuerpo de la petición tiene un límite de 1 MB. Por encima de eso — por el Content-Length declarado o por lo que de hecho llega — la respuesta es 413 con { "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 1048576 bytes)" } }. Ninguna tool recibe archivos, así que una llamada legítima queda muy por debajo.

Cabeceras de la respuesta​

CabeceraSignificado
MCP-Protocol-VersionRevisión en uso
Cache-ControlSiempre no-store
Retry-AfterSolo en 429 — ve Límite de peticiones

Métodos​

MétodoEra 2026-07-28Era 2025Para qué
server/discoversí—Versiones soportadas, capacidades del servidor e instrucciones
initialize—síNegociación de versión, capacidades del servidor e instrucciones
ping—síPrueba de vida; responde un resultado vacío
tools/listsísíLas tools que este llamador puede ejecutar, con su JSON Schema y anotaciones
tools/callsísíEjecuta una tool
resources/listsísíLos documentos de URI fija que este llamador puede leer
resources/templates/listsísíLas plantillas de URI (kobana://dashboards/portal_users/{id}, …) que este llamador puede leer
resources/readsísíLee un documento por su URI
prompts/listsísíLos guiones listos que este llamador puede usar
prompts/getsísíArma un guion con los argumentos dados
completion/completesísíSugerencias para un argumento de prompt o de plantilla

Resources, prompts y completions están en Resources y prompts.

Cualquier otro método es -32601 Method not found — con HTTP 404 en la era 2026-07-28; en el cuerpo de un 200 en la de 2025. El batching JSON-RPC no es soportado — fue removido en la revisión 2025-06-18, y un cuerpo en array se responde con 400.

Una notificación (mensaje sin id, como notifications/initialized) se acepta con 202 y cuerpo vacío, en las dos eras.

La autenticación va antes de cualquier método, incluido server/discover: sin token, la respuesta es el 401 con el desafío que lleva al cliente al login OAuth — ve Autenticación.

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 Dashboard Mágico: ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "kobana-dashboards",
"title": "Dashboard Mágico",
"version": "1.0.0"
}
}
} }

initialize (handshake de la 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-dashboards",
"title": "Dashboard Mágico",
"version": "1.0.0"
},
"instructions": "Tools for Dashboard Mágico: ..."
} }

tools/list​

Devuelve solo las tools que el llamador puede ejecutar, filtradas por los permisos de panel de quien autorizó el cliente. Cada tool lleva annotations, que es lo que hace que el cliente pida confirmación al usuario:

AnotaciónSignificado
readOnlyHintLa tool solo lee
destructiveHintLa tool borra, cierra, suspende o desactiva un registro, o envía un email o una invitación. El servidor pide confirmación en la era 2026-07-28 y rechaza la tool (confirmation_required) a un cliente que no puede confirmar
idempotentHintRepetir la llamada tiene el mismo efecto
openWorldHintSiempre false — la tool no alcanza nada fuera de Dashboard Mágico

tools/call​

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "dashboards_portal_users_list",
"arguments": { "status": "active", "per_page": 10,
"fields": ["email", "status", "created_at"] } } }

El resultado trae la respuesta JSON del endpoint como contenido de texto:

{ "jsonrpc": "2.0", "id": 2, "result": {
"content": [{ "type": "text", "text": "{ \"data\": [ ... ], \"meta\": { \"total\": 42, ... } }" }]
} }

Los errores sobre los que el llamador puede actuar vuelven con isError: true dentro del resultado, no como error JSON-RPC — ve Errores.

Despacho in-process​

Cada tool despacha in-process al handler de /api/v1 que implementa la operación. No hay segundo viaje HTTP ni segunda implementación: filtros de listado, reglas de upsert, validación, soft-delete y aislamiento por organización son el mismo código que corre la API REST. Una tool no puede divergir del endpoint que refleja.