Reference
Authentication
Which tools need a signed-in shopper, how Shop MCP asks for a token, how your own code gets one with the OAuth device flow, and what each authentication error means.
Shop MCP is an OAuth 2.0 protected resource. Ten of its tools run without a token. The other seven act for a signed-in shopper, and need an access token from Shop’s authorization server, https://accounts.shop.app, with the personal_agent scope.
| Tools | Token |
|---|---|
search_catalog, lookup_catalog, get_product, and the cart and checkout tools |
Optional. With a token, they run as the signed-in shopper. |
get_account, search_orders, and the five approval tools |
Required |
MCP clients such as Claude and ChatGPT get and send the token for you. This page is for code that calls Shop MCP itself, and for client authors.
Send the token
Put the token in an Authorization header on each call:
Authorization: Bearer <access token>
Send it only in that header and only to https://mcp.shop.com/. The server refuses a token in the query string or a cookie. Never put one in tool arguments. Shop MCP gets its own access to the catalog, merchants and Shop’s account services for each call, so the shopper’s token never needs to go anywhere else.
A token on a tool that doesn’t need one still has to be valid. An expired token fails the call with a 401 instead of running it signed out, so to keep shopping without the account, leave the header off.
How the server asks for a token
A call to a tool that needs a token, made without one, gets HTTP 401 with an empty body and this header. The tool doesn’t run.
WWW-Authenticate: Bearer resource_metadata="https://mcp.shop.com/.well-known/oauth-protected-resource", scope="personal_agent"
The protected resource metadata names the authorization server:
curl -s https://mcp.shop.com/.well-known/oauth-protected-resource
{
"resource": "https://mcp.shop.com",
"authorization_servers": ["https://accounts.shop.app"],
"scopes_supported": ["personal_agent"],
"bearer_methods_supported": ["header"],
"resource_name": "Shop"
}
Request tokens for the resource exactly as written there, https://mcp.shop.com, with no trailing slash. The authorization server publishes its own metadata at https://accounts.shop.app/.well-known/oauth-authorization-server. It accepts client ID metadata documents and has no dynamic client registration endpoint.
Once the shopper has signed in, send the original request again with the token.
Device flow
The OAuth device flow (RFC 8628) gets a token for code that can show the shopper a link but has no browser redirect of its own, such as a script or an agent in a terminal. It’s the same flow the CLI runs.
| Setting | Value |
|---|---|
| Device authorization endpoint | https://accounts.shop.app/oauth/device |
| Token endpoint | https://accounts.shop.app/oauth/token |
| Client ID | 5c733ab2-1903-400a-891e-7ba20c09e2a3 |
| Resource | https://mcp.shop.com |
| Scope | openid email personal_agent |
This is Shop’s public client, so there’s no client secret to send. It’s meant for a shopper’s own agent. An app that you distribute for other people to sign in to needs its own client registered with Shop.
Shop MCP checks for personal_agent. The CLI asks for that scope alone.
The examples below use curl and jq, and keep every response in a private temporary folder so that no code or token is printed or passed on a command line.
1. Request a code
umask 077
dir=$(mktemp -d)
curl -sS -m 30 https://accounts.shop.app/oauth/device -o "$dir/device.json" \
--data-urlencode 'client_id=5c733ab2-1903-400a-891e-7ba20c09e2a3' \
--data-urlencode 'resource=https://mcp.shop.com' \
--data-urlencode 'scope=openid email personal_agent' \
--data-urlencode "device_name=Sam's laptop"
jq '{verification_uri_complete, verification_uri, user_code, expires_in, interval}' "$dir/device.json"
device_name is how the connection appears in the shopper’s Shop account. Use 1 to 64 characters that the shopper will recognize, with no IDs, hostnames, user names or paths.
Show the shopper verification_uri_complete. If it’s missing, show verification_uri and have them enter user_code there. The response also has a device_code. That one is a credential: send it only to the token endpoint, and never show or log it.
2. Wait for approval
Ask the token endpoint for a token every interval seconds, five if the response gave none, until the shopper approves:
interval=$(jq -r '.interval // 5' "$dir/device.json")
while sleep "$interval"; do
jq -j '.device_code' "$dir/device.json" |
curl -sS -m 30 https://accounts.shop.app/oauth/token -o "$dir/token.json" \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
--data-urlencode 'client_id=5c733ab2-1903-400a-891e-7ba20c09e2a3' \
--data-urlencode 'device_code@-' || break
case $(jq -r '.error // empty' "$dir/token.json") in
authorization_pending) ;;
slow_down) interval=$((interval + 5)) ;;
*) break ;;
esac
done
jq '{error, token_type, scope, expires_in, signed_in: has("access_token")}' "$dir/token.json"
Don’t send resource again here: the token is for the resource named in step 1.
authorization_pending means the shopper hasn’t approved yet, and slow_down means to wait five seconds longer from then on. Any other response ends the loop: the shopper approved, declined, or let the code expire, or something else went wrong. If it didn’t end in a token, ask the shopper before you start a new flow.
3. Call Shop MCP
jq -r '"Authorization: Bearer " + .access_token' "$dir/token.json" |
curl -s https://mcp.shop.com/ -H @- \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: get_account' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_account","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
A result from get_account confirms the sign-in. A token on its own doesn’t, until a call with it succeeds.
The token expires after expires_in seconds. After that, calls fail with invalid_token, and getting a new token means running the flow again with the shopper’s agreement. Delete the folder when you’re done with it:
rm -r "$dir"
In an app, keep the token in memory or in a secret store instead of a file.
Authentication errors
The server checks the token before anything runs. When the check fails, the answer is an HTTP error instead of a JSON-RPC response:
| Response | Cause | What to do |
|---|---|---|
401, WWW-Authenticate with no error |
No token on a tool that needs one, or an Authorization scheme other than Bearer |
Offer sign-in, then send the request again with a token |
401, error="invalid_token" |
The token expired, was revoked, or was issued for another resource. This happens on every tool, including ones that run without a token. | Stop using the token. Ask the shopper before signing in again, or continue without the header. |
400, error="invalid_request" |
A malformed token, more than one, a token over 8,192 bytes, or a credential in the query string or a cookie | Fix the request |
403, error="insufficient_scope" |
The token doesn’t have personal_agent |
Sign in again with that scope, if the shopper agrees |
403 without insufficient_scope |
The server refused the request for another reason | Signing in won’t change it |
| 502 | Shop couldn’t check the token | Try again later. The token may still be good. |
| 503 | Shop is checking too many tokens at once | Try again later |
A few failures happen after the token is accepted, while Shop MCP gets access to the service a tool calls. They come back as a tool result with isError: true and a code in _meta["com.shopify/error"]:
code |
Meaning |
|---|---|
delegation_denied |
The shopper’s connection doesn’t allow this call, or the service refused it |
identity_unavailable |
Shop couldn’t get access for this call |
identity_overloaded |
Shop is handling too many sign-ins at once |
In each case the tool didn’t run, and the server didn’t fall back to running it signed out or try again itself. Errors and retries covers the other tool errors.
