Storefront MCP Coming soon
A store's address is also an MCP server for its customers' AI assistants: browse, order and pay without leaving the chat.
Connect
Each store has one address, https://www.satsrail.com/<store>/storefront. A browser
gets the store's page there; an MCP client POSTs JSON-RPC 2.0 to the same URL (MCP
2025-06-18, Streamable HTTP, one message per request, no batches). Add it to an assistant
as a custom connector:
{
"mcpServers": {
"la-casita": {
"type": "http",
"url": "https://www.satsrail.com/la-casita/storefront"
}
}
}
Every tool acts on the store in the URL. A store that is closed answers its tools with
store_unavailable; an address no store has answers 404.
Signing in
The catalog needs no sign-in. Ordering does: the customer connects the assistant once, through OAuth 2.1, with their SatsRail customer account, and the connection then works at every SatsRail store.
- Calling an ordering tool without a token answers
401withWWW-Authenticate: Bearer resource_metadata="https://www.satsrail.com/.well-known/oauth-protected-resource/<store>/storefront", scope="shop". - That document (RFC 9728) names the store's address as the resource, the authorization server
(
https://www.satsrail.com, RFC 8414 metadata at/.well-known/oauth-authorization-server) and the one scope,shop. - Register at
POST /oauth/register(RFC 7591) asking forscope: "shop". - Send the customer to
/oauth/authorizewithscope=shop, PKCE (S256only) and, optionally,resource=the store's address (RFC 8707). The customer signs in and approves. - Exchange the code at
POST /oauth/token. Access tokens last two hours; refresh tokens rotate.
Every request is checked again: a revoked connection, a locked account or a revoked token is refused at
once with 401. A shop token works only here, never on the merchant API or the
merchant MCP, and a merchant's API key or token is refused here. The customer disconnects an assistant
from Connected assistants on their account.
Tools
| Tool | Needs | What it does |
|---|---|---|
get_catalog | nothing | The store's products on sale with prices, taxes and product types. Filter by product_type, min_price and max_price (in the store's currency, not cents), sort (price_asc, price_desc) or q (name_i_cont, product_type_id_eq, price_gteq, price_lteq, s). Any other filter is refused, never ignored. |
create_order | sign-in | Opens the customer's one open order at this store, or returns it, with the shipping address and phone when the store asks for them |
add_order_item / update_order_item / remove_order_item | sign-in | Change what the open order holds, by a product's id from get_catalog; each answers the order with its totals |
get_order | sign-in | The open order, or one of the customer's orders here, with items, taxes, totals and status |
pay_order | sign-in | The link the customer opens to pay the order in bitcoin, or by card where the store takes cards. The assistant never pays. |
reset_order | sign-in | Empties the open order to start over |
list_my_orders | sign-in | The customer's orders at this store with payment and delivery status |
A catalog read without a token, from a script (every store's address answers on https://app.satsrail.com too):
curl -s https://app.satsrail.com/la-casita/storefront \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_catalog","arguments":{"sort":"price_asc"}}}'
Limits
Calls without a token are limited per IP address, like page views; calls with one, per token across
every store. Past either you get 429 with Retry-After. Each call a signed-in
customer's assistant makes is logged by tool name and outcome, never its arguments.