# 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](/es/code-theme/mcp/).

## 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](/openapi.json).

- Transporte: **Streamable HTTP sin sesión**. Cada `POST /mcp` lleva un
  mensaje JSON-RPC y recibe una respuesta JSON. `GET` y `DELETE` responden
  `405`.
- Protocolo: `2025-11-25` (también `2025-06-18` y `2025-03-26`).
- Las páginas web (pedidos con encabezado `Origin`) no llaman a `/mcp` ni 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:

```bash
curl -i -X POST https://mcp.hystore.com.br/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
```

```http
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:

```bash
curl https://mcp.hystore.com.br/.well-known/oauth-authorization-server
```

```json
{
  "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):

```bash
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"]}'
```

```json
{
  "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`, `http` de 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

```bash
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):

```text
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`.

> [!WARNING]
> El código vale **60 segundos** y una sola vez. Usar el mismo código de nuevo
> revoca todo lo que generó.

### 5. Cambiar el código por el token

```bash
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
```

```json
{
  "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

```bash
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.

```bash
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:

```bash
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

```json
{ "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

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": { "name": "read_theme_file", "arguments": { "path": "sections/hero.liquid" } }
}
```

```json
{
  "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`.

```json
{ "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`.

```json
{ "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`.

```json
{
  "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`: el `sha256` que leíste. Si el archivo cambió desde entonces, no
  se escribe nada y la respuesta trae `conflict`. `null` quiere decir "el
  archivo todavía no puede existir".
- `expected_revision` (opcional): la `revision` del borrador que leíste; si otra
  escritura pasó antes, no se escribe nada.
- Un archivo con error vuelve en `problems`, con `path`, `line`, `code` y
  `message`. No se escribe nada.

Éxito:

```json
{ "revision": 13, "files": [{ "path": "sections/black-friday.liquid", "sha256": "..." }], "warnings": [] }
```

Rechazo:

```json
{ "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`.

```json
{ "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.

```json
{ "name": "read_version_file", "arguments": { "version": 3, "path": "styles/theme.css" } }
```

```json
{ "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.

```json
{ "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, `429`
  con `Retry-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](/es/code-theme/cli/) no abren `/mcp`, y el token de la
  app no abre la [API del tema](/es/code-theme/api/).
- 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.
