Components
Webshop, API, MCP server, Auth0 proxy, backoffice, Auth0, and storage.
The API owns the shop database schema, both Durable Objects, Fern, Poppy conversations (JSON and native event streams), the Auth0 proxy’s internal notifications and the hourly leased jobs. The webshop is bound to the same shop database and Durable Objects and runs the capabilities only it performs; for shared capabilities it is a client of the API. MCP is a stateless API client. The backoffice keeps its own operator database and uses only the API for shop data. The Auth0 proxy issues credentials and never forwards shop business traffic. My Account credentials and provider calls stay in the webshop.
The contract and the client
@retail-demo/api (apps/api) is the Shop API package. Its exports map exposes only ./contracts* and ./client*, never ..
contracts/ is browser-safe and holds one nested oRPC contract: every procedure states its route (openapi metadata),
its callers and scopes (api metadata), its errors, a single object input and an output schema once. client/ is
createShopApi, an OpenAPILink client over the Worker service binding, with a deadline and problemOf for the
single error shape. It routes with the contract it is given: the MCP server and backoffice pass the full contract, which
also validates input before sending; the webshop passes minifiedContract (contracts/minified.json, generated by
oRPC’s minifyRouterContract and kept current by a test), so its bundle carries no schemas and the API validates. The webshop, MCP server and backoffice import these; nothing else may depend on
@retail-demo/api, and apps/api/src (the implementation) never imports the client. Token-endpoint code lives in
@retail-demo/oauth-client.
There is no dependency bag. The API’s initial context is { env, request, waitUntil, record, ports? } and typed
middleware stages add the rest; the MCP server uses the same idea with plain stage functions. See
How the API is built on oRPC.
OAuth resource helpers
@retail-demo/oauth-resource verifies RFC 9068 access tokens with typ: at+jwt, a pinned issuer and audience, and JWKS
fetched from an explicitly configured tenant-domain URL. It checks required claims and a bounded RFC 8693 actor
chain, and distinguishes invalid tokens from unavailable JWKS. It also builds bearer challenges and RFC 9728
protected-resource metadata. The API and the MCP server both use these helpers.
Each component has one job. The table is the short version; the sections below say what each one owns and what it deliberately does not.
| Component | Where it runs | Owns |
|---|---|---|
Webshop (apps/webshop) |
shop.sandrino.run |
Pages, sessions, cookies, OAuth redirects, Stripe Elements, My Account calls |
Shop API (apps/api) |
api.tarnandtimber.com |
Shared capabilities, schema, Durable Objects, jobs, caller authorization |
MCP server (apps/mcp) |
mcp.tarnandtimber.com |
MCP protocol, tool authorization, token exchange |
Proxy Auth0 (apps/proxy-auth0) |
login-shop and auth-shop hosts |
The Auth0 custom-domain front, Poppy credentials |
Backoffice (apps/backoffice) |
backoffice.tarnandtimber.com |
Operator dashboard, operator sessions and audit |
| Auth0 | Tenant with custom domain login-shop |
Identity, tokens, consent, tenant policy |
| D1 and Durable Objects | Cloudflare | Commerce data, AccountWriter, ConversationHub |
Settlement
Agent checkout with a saved card (checkoutSavedCard) runs in the API. Browser checkout, guest checkout, the 3-D
Secure return and WebMCP place_order run in the storefront through the same @retail-demo/shop-workflows operations. The
storefront owns confirmation, Stripe Elements and redirects. Stripe calls the API’s /webhooks/stripe, and the API’s
hourly jobs recover pending cancellations and financial operations. My Account has no API endpoint and stays in the
webshop.
Webshop
A TanStack Start application. It renders the storefront and keeps the browser session in the Auth0 SDK’s encrypted cookie, which is its own session: it receives
back-channel logout and keeps a revocation list in Workers KV. It composes browser and Poppy session management for the account holder, while the Auth0 proxy owns Poppy session/grant records and exact revocation. Validated logout notifications also deny linked Poppy grants through the Auth0 proxy. It
handles OAuth sign-in redirects, Stripe Elements, WebMCP registration, and all calls to Auth0’s My Account API.
It is bound to the shop database and to the API’s AccountWriter and ConversationHub, and runs every capability only the storefront performs (card controls, browser checkout, account security, activity, connected agents, invitations, delegated sign-in, guest sessions, receipts) through the shared domain operations, from server-only modules. For shared capabilities (catalog, cart, orders, addresses, the card list, identity, saved-card checkout and the checkout reads) it is a client of the API: signed-in shoppers use their own access token alone (the API keeps no website session state), guests use a storefront
machine-to-machine token, and agent browsers use the dedicated web-agent-forwarder token. It has no scheduled handler and does
not hold the conversation hub’s callback secret. It also exposes /api on the shop domain as a thin
proxy.
Shop API
A plain Worker fetch over an oRPC OpenAPIHandler, with no Hono. It serves the contract under /v1 and two OpenAPI
documents, and runs the domain workflows for the shared capabilities, Poppy conversations, webhooks and the backoffice reads, and owns the Durable Objects and scheduled jobs. It has no storefront-only endpoints. It classifies every token into a caller, resolves a live actor, applies guards, and runs the
operation. The pipeline is fixed and tested.
MCP server
A stateless Hono Worker (Hono stays here; the API no longer uses it) hosting an MCP server over streamable HTTP with the official SDK. It verifies the MCP client’s token, checks the
tool’s required scopes, exchanges the token for an API token on behalf of the user, calls the API and formats the
result. It holds no long-lived user credential. Each tool has a definition in apps/mcp/src/tools/ that owns its name,
input/output schemas, permissions, annotations and response renderer. Reusable MCP schema shapes live in
tools/schemas/, independently of the API contract. Calls use typed operations from the nested @retail-demo/api/client.
Proxy Auth0 (an Auth0 extension)
The Auth0 proxy fronts the Auth0 custom domain. It runs the Poppy sign-in flows, grants, sessions and Custom Token Exchange, and serves discovery, introspection and revocation. It issues and brokers credentials. It never forwards resource traffic to the API or MCP server.
Backoffice
The operator dashboard. Operators sign in with Okta. The backoffice server calls the API with its own machine token and tells it which operator is acting. It keeps its own database for operator sessions and the audit log.
Auth0
The identity provider: resource servers, clients, grants, consent, token issuance and tenant settings, all managed in Terraform. See How Auth0 is integrated.
Storage
ShopDatabase (D1) is bound to the API and the storefront. The API Worker declares the AccountWriter and
ConversationHub classes and the hourly cron; the storefront binds both classes from the API’s script and has no
scheduled handler. The MCP server has no storage binding, and the Auth0 proxy has no API, MCP or storefront forwarding
path. The backoffice binds only BackofficeDatabase, for staff sessions and audit. Source and built-bundle checks keep
database code out of the storefront’s browser build.
Diagnostic ownership
ShopDatabase also retains five diagnostic tables; it is not a new database or collector. BackofficeDatabase continues to own only operator sessions and the separate access ledger. “No shop database access” means no direct binding: auditors can investigate authorized diagnostics through API procedures. Storefront, MCP and backoffice emit through distinct named private API producer capabilities; the Auth0 proxy emits only synthetic HTTP streams, without app recorder or database access. Genuine direct/forwarded Auth0 events remain visibly separate from simulation. See diagnostic investigations for actual service/runtime attribution, source evidence, bounded raw demo policy and 30-day retention/restore behavior.