# Referência do MCP

Tudo o que um app de IA (ou você, escrevendo um) precisa para falar com a
Hystore: os endereços, o login OAuth passo a passo, o formato das mensagens e
cada ferramenta com exemplo. Para só conectar um app pronto, veja
[Conectar a IA](/pt/code-theme/mcp/).

## Endereços

| O quê | Endereço |
|---|---|
| Servidor MCP | `POST https://mcp.hystore.com.br/mcp` |
| Metadados do recurso (RFC 9728) | `GET https://mcp.hystore.com.br/.well-known/oauth-protected-resource/mcp` |
| Metadados do login (RFC 8414) | `GET https://mcp.hystore.com.br/.well-known/oauth-authorization-server` |
| Registro do app (RFC 7591) | `POST https://mcp.hystore.com.br/v1/oauth/register` |
| Tela de autorização | `https://app.hystore.com.br/oauth/authorize` |
| Token | `POST https://mcp.hystore.com.br/v1/oauth/token` |
| Sair (revogar) | `POST https://mcp.hystore.com.br/v1/oauth/revoke` |

Todas as rotas também estão na [OpenAPI](/openapi.json).

- Transporte: **Streamable HTTP sem sessão**. Cada `POST /mcp` leva uma mensagem
  JSON-RPC e recebe uma resposta JSON. `GET` e `DELETE` respondem `405`.
- Protocolo: `2025-11-25` (também `2025-06-18` e `2025-03-26`).
- Páginas da web (pedidos com cabeçalho `Origin`) não chamam `/mcp` nem o
  token: chame de um programa, não do navegador.

## Como o login funciona

É OAuth 2.1 com PKCE, do jeito que a especificação do MCP pede. Os apps prontos
fazem tudo sozinhos; os passos abaixo mostram o que acontece, com comandos que
você pode rodar no terminal.

### 1. Descobrir onde entrar

Sem token, `/mcp` responde `401` e aponta para os metadados:

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

Os metadados dizem quem faz o login (`authorization_servers`), e os do servidor
de login trazem os endereços de autorização, token e 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 o app

O app se registra uma vez e recebe um `client_id` público (sem segredo):

```bash
curl -X POST https://mcp.hystore.com.br/v1/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"Meu app","redirect_uris":["http://127.0.0.1:8765/callback"]}'
```

```json
{
  "client_id": "hys_cl_...",
  "client_name": "Meu app",
  "redirect_uris": ["http://127.0.0.1:8765/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}
```

- O endereço de volta precisa ser `https`, `http` de loopback (`127.0.0.1`,
  `localhost`, `[::1]`) ou o esquema próprio de um app (`meuapp:/callback`).
- O nome aparece na tela de autorização marcado como *informado pelo app*.
- Um app registrado e nunca usado some em 30 dias.

### 3. Gerar o 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 pessoa autorizar

Abra no navegador (uma linha só):

```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=<um valor aleatório>
```

A pessoa entra no painel, escolhe a loja e autoriza. O navegador volta para o
seu endereço com `?code=hys_ac_...&state=...`. Confira o `state`.

> [!WARNING]
> O código vale **60 segundos** e uma vez só. Usar o mesmo código de novo
> derruba tudo o que ele gerou.

### 5. Trocar o código pelo 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"
}
```

O token de acesso vale uma hora e só abre `/mcp`.

### 6. Renovar e sair

```bash
curl -X POST https://mcp.hystore.com.br/v1/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=hys_rt_...
```

Cada renovação devolve um par novo e invalida o anterior. Usar de novo um
`refresh_token` já trocado derruba a sessão inteira (alguém copiou o token).
A sessão dura 30 dias desde o último uso.

```bash
curl -X POST https://mcp.hystore.com.br/v1/oauth/revoke -d token=hys_rt_...
```

## Mensagens

Cada chamada é um `POST /mcp` com o token e uma mensagem 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": "meu-app", "version": "1.0.0" } } }
```

A resposta traz `serverInfo` (`hystore`), a capacidade `tools` e
`instructions`: um texto curto que explica à IA como trabalhar com o tema
(ler o guia primeiro, editar o rascunho, mostrar a prévia, pedir publicação).

### 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..." }
  }
}
```

Uma ferramenta que recusa algo responde com `"isError": true` e o motivo no
texto (e em `structuredContent`, quando há detalhes). Erros de protocolo
(método desconhecido, ferramenta que não existe) voltam como `error` do
JSON-RPC.

## Ferramentas

Os nomes dos argumentos estão em `snake_case`. Escopo é a permissão que a
conexão precisa ter.

### read_theme_guide

Lê o guia do tema (em inglês). Sem argumentos traz o guia inteiro; com
`section` (parte do título de uma seção, como `Sections`, `Data` ou `Apps`), só
aquela parte. Escopo: `theme:read`.

```json
{ "name": "read_theme_guide", "arguments": { "section": "Sections" } }
```

### get_store

Nome e endereço da loja e as permissões desta conexão. Sem argumentos. Escopo:
`theme:read`.

### list_theme_files

Os arquivos do rascunho com `sha256`, tamanho e a `revision` do rascunho. Sem
argumentos. Escopo: `theme:read`.

### read_theme_file

Um arquivo do rascunho com o `sha256`, que você devolve como `base_sha` ao
gravar. Escopo: `theme:read`.

```json
{ "name": "read_theme_file", "arguments": { "path": "layout/theme.liquid" } }
```

### write_theme_files

Cria, muda ou apaga (`content: null`) até 100 arquivos de uma vez: todos ou
nenhum. Escopo: `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`: o `sha256` que você leu. Se o arquivo mudou desde então, nada é
  gravado e a resposta traz `conflict`. `null` quer dizer "o arquivo ainda não
  pode existir".
- `expected_revision` (opcional): a `revision` do rascunho que você leu; se
  outra gravação passou na frente, nada é gravado.
- Um arquivo com erro volta em `problems`, com `path`, `line`, `code` e
  `message`. Nada é gravado.

Sucesso:

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

Recusa:

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

Confere o rascunho inteiro (a mesma conferência de antes de publicar): erros e
avisos com arquivo e linha. Sem argumentos. Escopo: `theme:read`.

### get_preview_link

Um link de 24 horas que abre o rascunho no endereço real da loja e recarrega
sozinho a cada mudança. Entregue para a pessoa olhar. Sem argumentos. Escopo:
`theme:read`.

### request_publish

Pede a publicação do rascunho atual; alguém com permissão aprova em
Personalização > Tema em código. Escopo: `theme:publish-request`.

```json
{ "name": "request_publish", "arguments": { "note": "Banner da Black Friday no topo da home" } }
```

### list_versions, read_version_file, restore_version

As últimas 30 versões publicadas; um arquivo como estava numa versão
(`content: null` quando ela não tinha o arquivo); e trazer uma versão de volta
para o **rascunho** (a loja no ar não muda até uma publicação ser aprovada).
Escopos: `theme:read` para ler, `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

Imagens e fontes do rascunho, com o endereço público. Use no tema como
`{{ 'nome.webp' | asset_url }}` ou `url(assets/nome.webp)`. Escopos:
`theme:read` para listar, `theme:write` para enviar e apagar.

```json
{ "name": "upload_asset", "arguments": { "path": "assets/banner.webp", "base64": "UklGRl..." } }
```

PNG, JPEG, WebP, GIF e AVIF até 3 MB; WOFF e WOFF2 até 1 MB. O tipo é lido do
conteúdo, não do nome. SVG não entra como arquivo de mídia.

### read_catalog

O que um visitante vê da loja: categorias e produtos ativos (id, nome, slug,
preço em centavos, imagem). Use os ids em `data-hystore-add-to-cart` e os slugs
em `{% load products, category: '...' %}`. Sem argumentos. Escopo:
`theme:read`.

## Limites

- 120 leituras e 30 gravações por minuto por conexão; acima disso, `429` com
  `Retry-After` (em segundos).
- Arquivos: 256 KB cada, 400 arquivos, 5 MB de texto. Mídia: 100 arquivos,
  40 MB.
- Uma gravação leva até 100 mudanças.

## Segurança

- O token do app só abre `/mcp`. As chaves e as sessões da
  [ferramenta hystore](/pt/code-theme/cli/) não abrem `/mcp`, e o token do app
  não abre a [API do tema](/pt/code-theme/api/).
- Nenhuma ferramenta mexe em pedidos, pagamentos, jogadores, cupons ou
  configurações, e nenhuma publica sozinha.
- O texto que vem da loja (nomes de produto, descrições) é tratado como dado,
  nunca como instrução para a IA.
