Referencia del MCP
Todo lo que una app de IA (o tú, escribiendo una) necesita para hablar con Hystore: las direcciones, el inicio de sesión OAuth paso a paso, el formato de los mensajes y cada herramienta con ejemplo. Para solo conectar una app lista, mira Conectar la IA.
Direcciones
| Qué | Dirección |
|---|---|
| Servidor MCP | POST https://mcp.hystore.com.br/mcp |
| Metadatos del recurso (RFC 9728) | GET https://mcp.hystore.com.br/.well-known/oauth-protected-resource/mcp |
| Metadatos del inicio de sesión (RFC 8414) | GET https://mcp.hystore.com.br/.well-known/oauth-authorization-server |
| Registro de la app (RFC 7591) | POST https://mcp.hystore.com.br/v1/oauth/register |
| Pantalla de autorización | https://app.hystore.com.br/oauth/authorize |
| Token | POST https://mcp.hystore.com.br/v1/oauth/token |
| Cerrar sesión (revocar) | POST https://mcp.hystore.com.br/v1/oauth/revoke |
Todas las rutas también están en la OpenAPI.
- Transporte: Streamable HTTP sin sesión. Cada
POST /mcplleva un mensaje JSON-RPC y recibe una respuesta JSON.GETyDELETEresponden405. - Protocolo:
2025-11-25(también2025-06-18y2025-03-26). - Las páginas web (pedidos con encabezado
Origin) no llaman a/mcpni al token: llama desde un programa, no desde el navegador.
Cómo funciona el inicio de sesión
Es OAuth 2.1 con PKCE, como pide la especificación del MCP. Las apps listas hacen todo solas; los pasos de abajo muestran lo que pasa, con comandos que puedes ejecutar en la terminal.
1. Descubrir dónde iniciar sesión
Sin token, /mcp responde 401 y apunta a los metadatos:
curl -i -X POST https://mcp.hystore.com.br/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://mcp.hystore.com.br/.well-known/oauth-protected-resource/mcp", scope="theme:read theme:write theme:publish-request"
Los metadatos dicen quién hace el inicio de sesión (authorization_servers), y
los del servidor de inicio de sesión traen las direcciones de autorización,
token y registro:
curl https://mcp.hystore.com.br/.well-known/oauth-authorization-server
{
"issuer": "https://mcp.hystore.com.br",
"authorization_endpoint": "https://app.hystore.com.br/oauth/authorize",
"token_endpoint": "https://mcp.hystore.com.br/v1/oauth/token",
"registration_endpoint": "https://mcp.hystore.com.br/v1/oauth/register",
"revocation_endpoint": "https://mcp.hystore.com.br/v1/oauth/revoke",
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["theme:read", "theme:write", "theme:publish-request"]
}
2. Registrar la app
La app se registra una vez y recibe un client_id público (sin secreto):
curl -X POST https://mcp.hystore.com.br/v1/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name":"Mi app","redirect_uris":["http://127.0.0.1:8765/callback"]}'
{
"client_id": "hys_cl_...",
"client_name": "Mi app",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}
- La dirección de vuelta tiene que ser
https,httpde loopback (127.0.0.1,localhost,[::1]) o el esquema propio de una app (miapp:/callback). - El nombre aparece en la pantalla de autorización marcado como informado por la app.
- Una app registrada y nunca usada se borra en 30 días.
3. Generar el PKCE
VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
4. Mandar a la persona a autorizar
Abre en el navegador (una sola línea):
https://app.hystore.com.br/oauth/authorize?response_type=code&client_id=hys_cl_...&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&code_challenge=<CHALLENGE>&code_challenge_method=S256&scope=theme%3Aread%20theme%3Awrite%20theme%3Apublish-request&resource=https%3A%2F%2Fmcp.hystore.com.br%2Fmcp&state=<un valor aleatorio>
La persona entra al panel, elige la tienda y autoriza. El navegador vuelve a
tu dirección con ?code=hys_ac_...&state=.... Revisa el state.
5. Cambiar el código por el token
curl -X POST https://mcp.hystore.com.br/v1/oauth/token \
-d grant_type=authorization_code \
-d code=hys_ac_... \
-d client_id=hys_cl_... \
-d redirect_uri=http://127.0.0.1:8765/callback \
-d code_verifier="$VERIFIER" \
-d resource=https://mcp.hystore.com.br/mcp
{
"access_token": "hys_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "hys_rt_...",
"scope": "theme:read theme:write theme:publish-request"
}
El token de acceso vale una hora y solo abre /mcp.
6. Renovar y cerrar sesión
curl -X POST https://mcp.hystore.com.br/v1/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=hys_rt_...
Cada renovación devuelve un par nuevo e invalida el anterior. Usar de nuevo un
refresh_token ya cambiado revoca la sesión entera (alguien copió el token).
La sesión dura 30 días desde el último uso.
curl -X POST https://mcp.hystore.com.br/v1/oauth/revoke -d token=hys_rt_...
Mensajes
Cada llamada es un POST /mcp con el token y un mensaje JSON-RPC:
curl -X POST https://mcp.hystore.com.br/mcp \
-H "Authorization: Bearer hys_at_..." \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
initialize
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": { "name": "mi-app", "version": "1.0.0" } } }
La respuesta trae serverInfo (hystore), la capacidad tools e
instructions: un texto corto que le explica a la IA cómo trabajar con el
tema (leer la guía primero, editar el borrador, mostrar la vista previa, pedir
publicación).
tools/call
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": { "name": "read_theme_file", "arguments": { "path": "sections/hero.liquid" } }
}
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [{ "type": "text", "text": "path: sections/hero.liquid\nsha256: 3f1c...\n\n<section class=\"hero\">..." }],
"structuredContent": { "path": "sections/hero.liquid", "content": "<section class=\"hero\">...", "sha256": "3f1c..." }
}
}
Una herramienta que rechaza algo responde con "isError": true y el motivo en
el texto (y en structuredContent, cuando hay detalles). Los errores de
protocolo (método desconocido, herramienta que no existe) vuelven como error
de JSON-RPC.
Herramientas
Los nombres de los argumentos están en snake_case. El alcance es el permiso
que la conexión necesita.
read_theme_guide
Lee la guía del tema (en inglés). Sin argumentos trae la guía entera; con
section (parte del título de una sección, como Sections, Data o Apps),
solo esa parte. Alcance: theme:read.
{ "name": "read_theme_guide", "arguments": { "section": "Sections" } }
get_store
Nombre y dirección de la tienda y los permisos de esta conexión. Sin
argumentos. Alcance: theme:read.
list_theme_files
Los archivos del borrador con sha256, tamaño y la revision del borrador.
Sin argumentos. Alcance: theme:read.
read_theme_file
Un archivo del borrador con su sha256, que devuelves como base_sha al
escribir. Alcance: theme:read.
{ "name": "read_theme_file", "arguments": { "path": "layout/theme.liquid" } }
write_theme_files
Crea, cambia o borra (content: null) hasta 100 archivos de una vez: todos o
ninguno. Alcance: theme:write.
{
"name": "write_theme_files",
"arguments": {
"changes": [
{ "path": "sections/black-friday.liquid", "content": "<section class=\"bf\">...</section>\n{% schema %}{\"name\":\"Black Friday\",\"settings\":[]}{% endschema %}", "base_sha": null },
{ "path": "templates/home.json", "content": "{\"sections\":{\"bf\":{\"type\":\"black-friday\"}},\"order\":[\"bf\"]}", "base_sha": "9a0b..." },
{ "path": "sections/old-banner.liquid", "content": null }
],
"expected_revision": 12
}
}
base_sha: elsha256que leíste. Si el archivo cambió desde entonces, no se escribe nada y la respuesta traeconflict.nullquiere decir "el archivo todavía no puede existir".expected_revision(opcional): larevisiondel borrador que leíste; si otra escritura pasó antes, no se escribe nada.- Un archivo con error vuelve en
problems, conpath,line,codeymessage. No se escribe nada.
Éxito:
{ "revision": 13, "files": [{ "path": "sections/black-friday.liquid", "sha256": "..." }], "warnings": [] }
Rechazo:
{ "refused": true, "problems": [{ "path": "sections/black-friday.liquid", "line": 3, "code": "html_event_handler", "message": "Event attributes (onclick, onload...) are not allowed. Use data-hystore-* components.", "severity": "error" }] }
check_theme
Revisa el borrador entero (la misma revisión de antes de publicar): errores y
avisos con archivo y línea. Sin argumentos. Alcance: theme:read.
get_preview_link
Un enlace de 24 horas que abre el borrador en la dirección real de la tienda y
se recarga solo con cada cambio. Entrégalo a la persona para que mire. Sin
argumentos. Alcance: theme:read.
request_publish
Pide la publicación del borrador actual; alguien con permiso aprueba en
Personalización > Tema en código. Alcance: theme:publish-request.
{ "name": "request_publish", "arguments": { "note": "Banner de Black Friday arriba en el inicio" } }
list_versions, read_version_file, restore_version
Las últimas 30 versiones publicadas; un archivo como estaba en una versión
(content: null cuando ella no tenía el archivo); y volver una versión al
borrador (la tienda en el aire no cambia hasta que se apruebe una
publicación). Alcances: theme:read para leer, theme:write para restaurar.
{ "name": "read_version_file", "arguments": { "version": 3, "path": "styles/theme.css" } }
{ "name": "restore_version", "arguments": { "version": 3 } }
list_assets, upload_asset, delete_asset
Imágenes y fuentes del borrador, con su dirección pública. Úsalas en el tema
como {{ 'nombre.webp' | asset_url }} o url(assets/nombre.webp). Alcances:
theme:read para listar, theme:write para enviar y borrar.
{ "name": "upload_asset", "arguments": { "path": "assets/banner.webp", "base64": "UklGRl..." } }
PNG, JPEG, WebP, GIF y AVIF hasta 3 MB; WOFF y WOFF2 hasta 1 MB. El tipo se lee del contenido, no del nombre. SVG no entra como archivo de medios.
read_catalog
Lo que un visitante ve de la tienda: categorías y productos activos (id,
nombre, slug, precio en centavos, imagen). Usa los ids en
data-hystore-add-to-cart y los slugs en {% load products, category: '...' %}.
Sin argumentos. Alcance: theme:read.
Límites
- 120 lecturas y 30 escrituras por minuto por conexión; arriba de eso,
429conRetry-After(en segundos). - Archivos: 256 KB cada uno, 400 archivos, 5 MB de texto. Medios: 100 archivos, 40 MB.
- Una escritura lleva hasta 100 cambios.
Seguridad
- El token de la app solo abre
/mcp. Las llaves y las sesiones de la herramienta hystore no abren/mcp, y el token de la app no abre la API del tema. - Ninguna herramienta toca pedidos, pagos, jugadores, cupones ni configuración, y ninguna publica sola.
- El texto que viene de la tienda (nombres de producto, descripciones) se trata como dato, nunca como instrucción para la IA.
