Why
attestomcp-from-scratch is 28 lines because the storefront module hides the whole ceremony. But every real deployment re-implements the same infrastructure our shopping demo hand-rolls — persistence and a live catalog loader — because createStorefront doesn't own them. That makes "using AttestoMCP" look far heavier than the quickstart.
Goal: make createStorefront accept pluggable stores / sources so consumers never hand-write adapters, and the demo collapses back toward the 28-line quickstart.
Children
Design
One combined spec (via spec-kit) covering the shared seam — pluggable stores + catalog sources — with these invariants held:
- In-memory stores and the static catalog array remain the zero-config defaults (quickstart unchanged).
- Explicit store/catalog injection still supported and precedence-clear (custom-backend escape hatch).
- Security: per-order verification scoping (invariant 4), server-side price/age re-derivation (invariant 2), catalog fail-closed on empty/unreachable load.
@upstash/redis / Firebase Admin stay peer/optional so in-memory/static users keep a lean install.
API contract (maintainer-owned — anchor for the spec)
The public signatures below are the contract; the spec fleshes out behavior, edge cases, and tests within them. Any change to these signatures needs maintainer (@dzuluaga) sign-off — this is a separate, explicit line in the review gate, because a published API shape is not reversible the way an implementation is.
interface CreateStorefrontOptions {
// …existing: catalog, reviews, baseUrl, signingKey, cartStore, orderStore, … …
// #27 persistence — where cart/order/verification state lives; in-memory is the default
storage?: StorageAdapter;
// #28 catalog — a SOURCE, not only a static array; the array stays the default
catalog?: Product[] | CatalogSource;
}
// symmetric factories — the backend name lives in the helper, never the option
// #27 helper — builds cart + created/completed-order + verification stores from one handle
function redisStorage(opts: {
url: string;
token: string; // @upstash/redis-compatible (HTTP/REST)
namespace?: string; // key prefix; isolates deployments on a shared instance
}): StorageAdapter;
// #28 helper — the module owns loader + TTL cache + fail-closed
function firestoreCatalog(opts: {
credentials?: FirebaseAdminCreds; // else GOOGLE_APPLICATION_CREDENTIALS
collection?: string;
ttlMs?: number; // default 300_000
}): CatalogSource;
interface StorageAdapter {
// provides the four stores createStorefront needs, each scoped per order id (Security invariant 4)
}
interface CatalogSource {
load(): Promise<Product[]>; // fail-closed: throws on empty/unreachable cold load
}
Precedence / defaults (also maintainer-owned):
- Explicit store injection (
cartStore / createdOrderStore / orderStore / verificationStore) wins over storage — the custom-backend escape hatch.
- No
storage and no explicit stores → in-memory (zero-config default; quickstart unchanged).
catalog as Product[] → static (default); as CatalogSource → dynamic.
@upstash/redis and firebase-admin stay peer/optional deps.
- Naming rule: options name the concept (
storage, catalog); the backend appears only in the factory (redisStorage, firestoreCatalog) — so a future postgresStorage() / sqlCatalog() needs no interface change.
Related
Why
attestomcp-from-scratchis 28 lines because the storefront module hides the whole ceremony. But every real deployment re-implements the same infrastructure our shopping demo hand-rolls — persistence and a live catalog loader — becausecreateStorefrontdoesn't own them. That makes "using AttestoMCP" look far heavier than the quickstart.Goal: make
createStorefrontaccept pluggable stores / sources so consumers never hand-write adapters, and the demo collapses back toward the 28-line quickstart.Children
createStorefront({ storage: redisStorage(…) })builds cart/order/verification stores internallycreateStorefront({ catalog: firestoreCatalog(…) })owns the loader + TTL cache + fail-closedmcp-apps-shopping-demoto consume both (flip to published@openmobilehub/credentagent-*^0.2.x, delete its 3 Redis adapter classes +catalog-store.ts/firebase-admin.ts/catalog-source.ts)Design
One combined spec (via spec-kit) covering the shared seam — pluggable stores + catalog sources — with these invariants held:
@upstash/redis/ Firebase Admin stay peer/optional so in-memory/static users keep a lean install.API contract (maintainer-owned — anchor for the spec)
The public signatures below are the contract; the spec fleshes out behavior, edge cases, and tests within them. Any change to these signatures needs maintainer (@dzuluaga) sign-off — this is a separate, explicit line in the review gate, because a published API shape is not reversible the way an implementation is.
Precedence / defaults (also maintainer-owned):
cartStore/createdOrderStore/orderStore/verificationStore) wins overstorage— the custom-backend escape hatch.storageand no explicit stores → in-memory (zero-config default; quickstart unchanged).catalogasProduct[]→ static (default); asCatalogSource→ dynamic.@upstash/redisand firebase-admin stay peer/optional deps.storage,catalog); the backend appears only in the factory (redisStorage,firestoreCatalog) — so a futurepostgresStorage()/sqlCatalog()needs no interface change.Related
AttestoMcp→AttestoMCP)mcp-apps-shopping-demo) — the reference implementation being folded in