MCP reference
Everything an AI app (or you, writing one) needs to talk to Hystore: the addresses, the OAuth sign-in step by step, the message format and every tool with an example. To just connect a ready app, see Connect your AI.
Addresses
| What | Address |
|---|---|
| MCP server | POST https://mcp.hystore.com.br/mcp |
| Resource metadata (RFC 9728) | GET https://mcp.hystore.com.br/.well-known/oauth-protected-resource/mcp |
| Sign-in metadata (RFC 8414) | GET https://mcp.hystore.com.br/.well-known/oauth-authorization-server |
| App registration (RFC 7591) | POST https://mcp.hystore.com.br/v1/oauth/register |
| Authorization screen | https://app.hystore.com.br/oauth/authorize |
| Token | POST https://mcp.hystore.com.br/v1/oauth/token |
| Sign out (revoke) | POST https://mcp.hystore.com.br/v1/oauth/revoke |
Every route is also in the OpenAPI.
- Transport: Streamable HTTP without sessions. Each
POST /mcpcarries one JSON-RPC message and gets a JSON answer.GETandDELETEanswer405. - Protocol:
2025-11-25(also2025-06-18and2025-03-26). - Web pages (requests with an
Originheader) cannot call/mcpor the token endpoint: call from a program, not from the browser.
How the sign-in works
It is OAuth 2.1 with PKCE, the way the MCP specification asks. Ready apps do all of it by themselves; the steps below show what happens, with commands you can run in a terminal.
1. Find where to sign in
Without a token, /mcp answers 401 and points to the metadata:
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"
The metadata says who handles the sign-in (authorization_servers), and the
sign-in server's metadata has the authorization, token and registration
addresses:
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. Register the app
The app registers once and gets a public client_id (no secret):
curl -X POST https://mcp.hystore.com.br/v1/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name":"My app","redirect_uris":["http://127.0.0.1:8765/callback"]}'
{
"client_id": "hys_cl_...",
"client_name": "My app",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}
- The redirect address must be
https, loopbackhttp(127.0.0.1,localhost,[::1]) or an app's own scheme (myapp:/callback). - The name shows on the authorization screen marked as given by the app.
- An app that registers and is never used is removed after 30 days.
3. Make the PKCE pair
VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
4. Send the person to authorize
Open in the browser (one line):
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=<a random value>
The person signs in to the dashboard, picks the store and authorizes. The
browser comes back to your address with ?code=hys_ac_...&state=.... Check
the state.
5. Exchange the code for a 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"
}
The access token lasts one hour and only opens /mcp.
6. Refresh and sign out
curl -X POST https://mcp.hystore.com.br/v1/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=hys_rt_...
Each refresh returns a new pair and invalidates the old one. Using a
refresh_token that was already exchanged revokes the whole session (someone
copied the token). The session lasts 30 days from the last use.
curl -X POST https://mcp.hystore.com.br/v1/oauth/revoke -d token=hys_rt_...
Messages
Each call is a POST /mcp with the token and a JSON-RPC message:
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": "my-app", "version": "1.0.0" } } }
The answer has serverInfo (hystore), the tools capability and
instructions: a short text that tells the AI how to work with the theme
(read the guide first, edit the draft, show the preview, ask to publish).
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..." }
}
}
A tool that refuses something answers with "isError": true and the reason
in the text (and in structuredContent when there are details). Protocol
errors (unknown method, a tool that does not exist) come back as a JSON-RPC
error.
Tools
Argument names are in snake_case. Scope is the permission the connection
needs.
read_theme_guide
Reads the theme guide (in English). Without arguments it returns the whole
guide; with section (part of a section title, such as Sections, Data or
Apps), only that part. Scope: theme:read.
{ "name": "read_theme_guide", "arguments": { "section": "Sections" } }
get_store
The store's name and address and this connection's permissions. No
arguments. Scope: theme:read.
list_theme_files
The draft's files with sha256, size and the draft revision. No
arguments. Scope: theme:read.
read_theme_file
One draft file with its sha256, which you send back as base_sha when
writing. Scope: theme:read.
{ "name": "read_theme_file", "arguments": { "path": "layout/theme.liquid" } }
write_theme_files
Creates, changes or deletes (content: null) up to 100 files at once: all or
nothing. Scope: 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: thesha256you read. If the file changed since then, nothing is written and the answer hasconflict.nullmeans "the file must not exist yet".expected_revision(optional): the draftrevisionyou read; if another write got there first, nothing is written.- A file with an error comes back in
problems, withpath,line,codeandmessage. Nothing is written.
Success:
{ "revision": 13, "files": [{ "path": "sections/black-friday.liquid", "sha256": "..." }], "warnings": [] }
Refused:
{ "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
Checks the whole draft (the same check that runs before publishing): errors
and warnings with file and line. No arguments. Scope: theme:read.
get_preview_link
A 24-hour link that opens the draft on the store's real address and reloads
by itself after each change. Give it to the person to look at. No arguments.
Scope: theme:read.
request_publish
Asks to publish the current draft; someone with permission approves in
Customization > Code theme. Scope: theme:publish-request.
{ "name": "request_publish", "arguments": { "note": "Black Friday banner at the top of the home" } }
list_versions, read_version_file, restore_version
The last 30 published versions; a file as it was in a version
(content: null when that version did not have it); and bringing a version
back to the draft (the live store does not change until a publish is
approved). Scopes: theme:read to read, theme:write to restore.
{ "name": "read_version_file", "arguments": { "version": 3, "path": "styles/theme.css" } }
{ "name": "restore_version", "arguments": { "version": 3 } }
list_assets, upload_asset, delete_asset
Images and fonts of the draft, with their public address. Use them in the
theme as {{ 'name.webp' | asset_url }} or url(assets/name.webp). Scopes:
theme:read to list, theme:write to upload and delete.
{ "name": "upload_asset", "arguments": { "path": "assets/banner.webp", "base64": "UklGRl..." } }
PNG, JPEG, WebP, GIF and AVIF up to 3 MB; WOFF and WOFF2 up to 1 MB. The type is read from the content, not the name. SVG is not accepted as a media file.
read_catalog
What a visitor sees of the store: active categories and products (id, name,
slug, price in cents, image). Use the ids with data-hystore-add-to-cart and
the slugs with {% load products, category: '...' %}. No arguments. Scope:
theme:read.
Limits
- 120 reads and 30 writes per minute per connection; above that,
429withRetry-After(in seconds). - Files: 256 KB each, 400 files, 5 MB of text. Media: 100 files, 40 MB.
- One write carries up to 100 changes.
Security
- The app token only opens
/mcp. The keys and sessions of the hystore tool do not open/mcp, and the app token does not open the theme API. - No tool touches orders, payments, players, coupons or settings, and none publishes by itself.
- Text that comes from the store (product names, descriptions) is treated as data, never as an instruction to the AI.
