HystoreWiki

5 min de leituraVer em Markdown

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.

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.

  • 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:

Terminal
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:

Terminal
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):

Terminal
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

Terminal
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ó):

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.

5. Trocar o código pelo token

Terminal
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

Terminal
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.

Terminal
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:

Terminal
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.

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 não abrem /mcp, e o token do app não abre a API do tema.
  • 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.