Skip to content
Tarn & Timber Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Was this page helpful?