# Storefront MCP

> Every SatsRail storefront is also an MCP server: a customer's AI assistant browses the store, builds an order and hands the customer a link to pay in bitcoin.

Source: https://www.satsrail.com/developers/guides/storefront-mcp/

A store's address is also an MCP server for its customers' AI assistants: browse, order and pay without leaving the chat.

> **Not open yet.** Storefronts open when SatsRail switches them on, for merchants on a plan that includes one. Until then every storefront answers "store unavailable". This page describes the server as it is built, so assistant developers can prepare.

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

1. Calling an ordering tool without a token answers `401` with `WWW-Authenticate: Bearer resource_metadata="https://www.satsrail.com/.well-known/oauth-protected-resource/<store>/storefront", scope="shop"`.
2. 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`.
3. Register at `POST /oauth/register` (RFC 7591) asking for `scope: "shop"`.
4. Send the customer to `/oauth/authorize` with `scope=shop`, PKCE (`S256` only) and, optionally, `resource=` the store's address (RFC 8707). The customer signs in and approves.
5. 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.
