An MCP shopping experience for Reboot-branded swag, built with Reboot and backed by Printful for real product data and on-demand fulfillment.
For the impatient:
- Set up your environment
- Configure Printful and secrets
- Run the application
- Generate a coupon code
- Connect an MCP client
- Running the tests
- Deploy to Reboot Cloud
The Reboot Store is an AI Chat App (MCP App) that lets users browse products, manage a shopping cart, and check out — all through an AI chat interface. It exposes three interactive UIs:
- Store — a product grid showing the products the model picked for the current request, with color and size selection per product.
- Cart — a cart view with item management, totals, a shipping address form, and a coupon code field.
- Order Confirmation — a receipt view after a successful purchase.
The backend uses Reboot's durable state to persist carts, coupon
codes, and orders across restarts. Product data is fetched live
from Printful on each list_products call. Anonymous auth
generates a unique user identity per MCP session.
Filtering is AI-driven, using a data tool + widget tool
split. list_products returns the full catalog with no server-
side filter; the client model reads it, picks the IDs whose
name or description match the user's intent, and then opens
browse_store with those product_ids. The widget filters
the cached catalog client-side to that subset. This pushes
natural-language understanding to the model — no regex,
stopwords, or synonym lists in the backend — and copes with any
phrasing ("show me hats", "what cold-weather gear do you have",
"the navy ones") that the model can interpret.
⚠️ Printful places real orders. When a checkout completes, this app calls the PrintfulPOST /ordersendpoint. Use a Printful store in draft mode (or a sandbox account) while developing so no merch actually ships.
reboot-swag-store/
├── api/reboot_swag_store/v1/
│ └── store.py # State models and API definition
├── backend/src/
│ ├── main.py # Application entrypoint
│ ├── constants.py # Shared constants
│ ├── printful.py # Printful API client
│ └── servicers/
│ └── store.py # Servicer implementations
└── frontend/
├── index.css # Shared theme
└── mcp/
├── store/ # Store browse UI
├── cart/ # Shopping cart UI
└── confirmation/ # Order confirmation UI
- User — per-MCP-session entry point. Methods to browse the
store (
browse_storeUI,list_products) and create a cart (create_cart). All auto-exposed as MCP tools. - Cart — per-user cart. Methods to
add_item,remove_item,get_cart,show_cart(UI), andcheckout. - CouponBook — singleton (id
coupon-book) that holds a pool of single-use coupon codes. Exposesgenerate_codes(admin only),validate_code, andredeem_code. On first startup, 20 codes are generated automatically. - Order — created on checkout. Stores receipt details and
runs a
fulfillworkflow that places the Printful order at-least-once.
- Python >= 3.10
- uv (Python package manager)
- Node.js and npm
- A Printful account with at least one sync product
From the reboot-swag-store/ directory:
uv synccd frontend && npm install && cd ..uv run rbt generate-
Create a Printful store (use draft mode during development).
-
Add at least one sync product so
list_productshas something to return. -
Create a Printful API token at https://www.printful.com/dashboard/developer/api-keys.
-
Copy
.env.exampleto.envand fill in the values:cp .env.example .env
# .env PRINTFUL_API_TOKEN=your_printful_api_token_here STORE_ADMIN_KEY=pick-any-secret-stringThe backend reads these env vars at request time. In Reboot Cloud, the same names are injected via
rbt cloud secret set(see "Deploy to Reboot Cloud" below), so the same code works in both environments.STORE_ADMIN_KEYis an app-local secret used to gate thegenerate_codesendpoint. Pick any value; you'll pass it as a bearer token when you generate coupon codes (see below).
You need two terminals, both starting from the reboot-swag-store/
directory.
Terminal 1 — backend:
uv run rbt dev runThis starts the Reboot backend and watches for file changes. See
.rbtrc for the full set of flags.
Terminal 2 — frontend (HMR):
cd frontend && npm run devThis starts the Vite dev server with hot module replacement. The backend proxies UI requests to Vite via Envoy.
Checkout requires a valid coupon code, which makes the order
free. Twenty codes are generated the first time the backend
starts; you can view them (and mint more) via the Reboot inspect
dashboard and a short curl call.
Open the inspect dashboard at http://localhost:9991/__/inspect. In the States tab:
- Select the
reboot_swag_store.v1.CouponBookstate type. - Click the
coupon-bookinstance. - The
codesfield lists every unused code.
Pick any code from that list to use at checkout.
CouponBook.generate_codes is not exposed as an MCP tool — it's
an admin operation. Call it over HTTP with your admin key as a
bearer token:
curl -XPOST http://localhost:9991/reboot_swag_store.v1.CouponBookMethods/GenerateCodes \
-H "x-reboot-state-ref: reboot_swag_store.v1.CouponBook:coupon-book" \
-H "Authorization: Bearer $STORE_ADMIN_KEY" \
-d '{}'The response contains 20 new six-digit codes. They're also
appended to the CouponBook state visible in the inspect
dashboard.
Once the app is running, open http://localhost:9991 and follow the setup wizard to connect a chat host (such as Claude or ChatGPT) to the app.
Once connected, try these prompts (substitute one of your coupon
codes for 123456):
- "Show me what's in the store."
- "Show me only the hats."
- "Create a cart and add the Reboot Hoodie in size L."
- "Show me my cart."
- "Check out. Ship to Jane Doe, 123 Main St, Seattle WA 98101, US, jane@example.com. Use coupon code 123456."
- "Show me the confirmation for my order."
cd backend && uv run pytestThe suite has two files:
backend/tests/printful_helpers_test.py— pure-function tests for the variant-parsing helper. Fast, no Reboot runtime.backend/tests/store_servicer_test.py— integration tests that spin up an in-process Reboot, set the admin-key env var to a known test value, and swapOrderServicer.fulfillfor a no-op so tests don't hit Printful. Covers cart add/remove/checkout,CartEmpty/InvalidCouponaborts, coupon single-use, the admin-gatedgenerate_codesendpoint, and thelist_products"full catalog, no server- side filter" contract (using a stubbedfetch_products).
The repo includes a Dockerfile and rbt serve / rbt cloud
configuration so the same code you ran locally can be deployed
to Reboot Cloud. You'll need an
invitation + API key — sign up at https://cloud.reboot.dev/.
The backend reads PRINTFUL_API_TOKEN and STORE_ADMIN_KEY
from its environment. In the cloud, set them once with rbt cloud secret set; Cloud injects each one into the application's
environment under its given name:
uv run rbt cloud secret set \
--api-key=YOUR_API_KEY \
--application-name=reboot-swag-store \
PRINTFUL_API_TOKEN=YOUR_PRINTFUL_TOKEN \
STORE_ADMIN_KEY=YOUR_ADMIN_KEYrbt cloud up builds the Dockerfile in this directory and
deploys the resulting image:
uv run rbt cloud up --api-key=YOUR_API_KEYThe flags --name=reboot-swag-store, --dockerfile=Dockerfile, and
friends come from .rbtrc. The output prints the URL of the
deployed application; point your MCP client at <URL>/mcp.
# The inspect dashboard works against Cloud too:
# <YOUR_URL>/__/inspect
uv run rbt cloud logs --api-key=YOUR_API_KEY
uv run rbt cloud down --api-key=YOUR_API_KEY # keep state
uv run rbt cloud down --api-key=YOUR_API_KEY --expunge # wipeFresh deploys will regenerate the initial 20 coupon codes on
first start; existing deploys persist codes (and carts, orders,
etc.) across rbt cloud up redeploys until you pass
--expunge.
To wipe all local persisted state (carts, coupons, orders, etc.):
uv run rbt dev expunge --name=reboot-swag-storeThe next rbt dev run will regenerate the initial 20 coupon
codes.