Skip to main content
Glama

wardrobe-mcp

CI

A self-hosted MCP server that gives Alexa+ a customer's wardrobe: what they own, what they've saved, what they could buy — and, the differentiator, what any of it looks like on them, rendered by the existing on-device virtual try-on pipeline (pravoobi/try-on).

"Alexa, I have a sangeet on Saturday. What should I wear? Budget eight thousand." → suggest_outfits reads out 3 options with reasons → "Show me the magenta one on me." → preview_look returns a rendered try-on on the Echo Show → "Save that." → "Compare them." → "Go with the magenta." → record_reaction(love) → Alexa+ offers the purchase link.

Thesis: Alexa+ is the stylist's voice; this server is the stylist's memory and eyes. Every tool is a thin, fast, structured lookup. Anything slow (rendering) is pre-computed.

Built for the Amazon Build, Ship, Shape hackathon — Alexa+ track, plus the AWS Builder and Open Source mini challenges. MIT licensed.

The sangeet flow in the simulated Alexa+ web app: suggest, preview, save, compare

Recorded 2026-09-08 against a locally-seeded server with RENDER_MODE=browser — every preview is the real try-on pipeline compositing the garment onto the model photo, not the local-mode fallback (garment shown beside the photo). The "Bedrock is unavailable" notices are real and honest, not staged — the account-verification blocker in docs/aws.md was still open at recording time, so src/web/ruleReasoner.ts answered instead of real Bedrock. Same real MCP tools either way.

Try it in a minute

Everything below is the live deployed server (AWS App Runner, us-east-1) — no signup, no API key, nothing to install.

  1. Open the simulated Alexa+ app → Click Connect → Continue as Praveen (a real OAuth 2.1 PKCE login against this server's own /oauth/*), then click the first suggested line: "I have a sangeet on Saturday. What should I wear? Budget eight thousand." Keep clicking down the list — suggest → preview → save → another → save → compare. Every grey TOOL CALL bubble is a real MCP tools/call over Streamable HTTP, and every image on the Echo Show panel is served from CloudFront.

  2. See the OAuth discovery document → — S256, the resource parameter, no Dynamic Client Registration: what Alexa+ requires.

  3. Check which commit is live → — the deployed GIT_SHA, baked in at image build. It tracks the last commit that changed the server; docs-only commits move main ahead without a redeploy.

Prefer to drive it as a raw MCP client? POST /mcp unauthenticated returns the 401 shape Alexa+ expects; the full OAuth + tools/call flow is in Auth.

Related MCP server: mcp-server-custody

Architecture

  Alexa+ (MCP client) ──Streamable HTTP + Bearer──► wardrobe-mcp (Node 22 + TS)
                                                     ├─ /mcp        MCP server (tools, resources, prompts)
                                                     ├─ /oauth/*    OAuth 2.1 AS (PKCE S256, resource param)
                                                     ├─ /.well-known/oauth-*   discovery
                                                     └─ /renders/*  rendered looks (dev; CloudFront in prod)
                          store/   SQLite (dev) │ DynamoDB (prod)
                          render/  queue → Playwright worker → drives try-on's own WebMCP tools

The renderer bridge: the try-on pipeline is browser-only (LiteRT.js, WebGPU/Wasm). We don't port it. The render worker launches headless Chromium, opens the deployed try-on app, and calls the page's own WebMCP tools via document.modelContext.tools.get("apply_tryon").execute(...), then snapshots the result canvas. One tool contract, two transports. Rendering is never in the request path — preview_look / compare_looks are cache reads; a miss enqueues and returns { status: "rendering" | "composing", retryAfterSeconds }.

Two render modes (RENDER_MODE):

mode

what it does

when

local (default)

composes a look card with sharp from the flat garment PNG + the customer photo — fast, offline, deterministic

dev, tests, demo

browser

the real bridge above — drives the deployed try-on app in headless Chromium

recording the demo, eyeballing real try-on output

stub

solid-colour placeholder

unit tests

browser mode needs Playwright's full Chromium (channel: "chromium"), not the default headless shell — the shell ships no GPU stack at all, so requestAdapter() returns null and the try-on app waits forever on a WebGPU device that never arrives. With the right binary it's ~13 s per render, not "slow because of CPU fallback" (that was our own wrong diagnosis for a while — see docs/friction-log.md #4a/#4b for the full trail). It stays opt-in and local stays the default because production runs on a 0.25 vCPU / 0.5 GB App Runner instance, which is too small to run Chromium plus an ML pipeline safely — not because it doesn't work.

Tool surface

Tool

Purpose

list_wardrobe

Clothes the customer owns/saved (filter by category / occasion / colour).

search_catalog

Ranked catalog search — occasion, colour, category, budget, free text.

suggest_outfits

Up to 3 outfits for an occasion, deterministically ranked (no LLM).

preview_look

A garment rendered on the customer's photo. Cache read.

save_look

Persist a look with a label.

list_looks

The looks saved this session.

compare_looks

2–3 saved looks side by side.

record_reaction

love / like / try_another / reject → guidance for the next turn.

add_to_wardrobe

Add a catalog garment; triggers background pre-render.

plan_week

Up to a week of events, one outfit each, never repeating a garment.

Resources: wardrobe://catalog, wardrobe://looks/{lookId}. Prompt: style_me.

Run it locally

npm install
cp .env.example .env
npm run seed      # demo user "Praveen" + try-on test photo + whole-catalog pre-render
npm run dev       # http://localhost:3000/mcp

npm run seed fetches the catalog garment PNGs and a test photo from the deployed try-on app (cached under data/assets/). Render one look to eyeball it: tsx scripts/render-one.ts dress-magenta-wrap-01 photo-01 (add --browser for the real bridge).

Or one command, no Node install needed:

docker compose up --build          # http://localhost:3000
docker compose run --rm seed       # once, in another terminal — demo user + pre-rendered looks

SQLite + local renders persist on the host under ./data and ./renders (both gitignored), so a later docker compose up picks up where you left off. seed uses the build image stage (has tsx + scripts/) rather than the slim production one.

Auth (OAuth 2.1)

A minimal OAuth 2.1 authorization server runs in the same process — exactly what Alexa+ expects, nothing it rejects:

  • authorization-code + PKCE S256 only; RFC 8707 resource mandatory, must equal the canonical MCP URL; public pre-registered clients (no Dynamic Client Registration)

  • GET /.well-known/oauth-authorization-server + /.well-known/oauth-protected-resource

  • GET /oauth/authorize → /link demo consent → POST /oauth/token (15-min access, 30-day refresh, rotating; all persisted hashed)

  • unauthenticated POST /mcp → 401 with no WWW-Authenticate (Amazon's quirk)

Ops: GET /healthz (add ?deep=1 to also touch the store — DynamoDB/SQLite — rather than just confirm the process is up), GET /version (package version + GIT_SHA, so it's obvious which commit a deployed instance is actually running — see friction log #11, a stale image was the whole bug). /link, /oauth/token, /api/simulate/turn and /mcp are all rate-limited per IP (src/auth/rateLimit.ts); graceful shutdown on SIGTERM/SIGINT so an App Runner deploy doesn't sever in-flight requests.

Dev shortcut: Authorization: Bearer dev_praveen works when ALLOW_DEV_TOKENS=true (auto-off when NODE_ENV=production). Point MCP Inspector at http://localhost:3000/mcp and it runs the full PKCE dance.

Expose it for Alexa+: cloudflared tunnel --url http://localhost:3000. When you do, restart the dev server with MCP_CANONICAL_URL set to the tunnel's https://… hostname (the OAuth resource param must match it exactly) and ALLOW_DEV_TOKENS=false (a publicly reachable instance shouldn't accept dev_* bearer tokens). Verified 2026-09-07: real OAuth 2.1 PKCE + tools/call round trip in ~150 ms through a quick tunnel — handy for local-machine testing; the real deployed instance is https://ba93fetekm.us-east-1.awsapprunner.com (see docs/aws.md).

Verify

npm run typecheck
npm test            # unit + OAuth + discovery + CORS + Store contract + e2e MCP client
                    # 86 pass; 9 DynamoDB-Local tests skip unless `npm run test:dynamo`
npm run smoke       # boots the server, runs OAuth + the full sangeet flow
npm run test:dynamo # Store contract against DynamoDB Local (needs Docker)

npm run smoke asserts: unauthenticated POST /mcp → 401 without WWW-Authenticate; discovery advertises S256; a full PKCE loop yields a token that works on /mcp; negotiated protocol ≥ 2025-11-25; every image tool a cache hit < 100 ms.

Status

  • Phase 1 — skeleton MCP over Streamable HTTP, catalog + wardrobe + suggest tools, SQLite store, smoke flow.

  • Phase 2 — renderer bridge (local sharp compositor + opt-in browser bridge to the try-on app's WebMCP tools), sharp compare sheets, cache + queue, whole-catalog pre-render on seed, inline image blocks. Catalog synced from pravoobi/try-on. Tool schemas frozen.

  • Phase 3 — OAuth 2.1 AS (PKCE S256, resource, refresh rotation, every rejection-path tested); DynamoDB Store (single-table, parity suite vs SQLite, verified against DynamoDB Local); S3 + CloudFront render storage; Bedrock garment tagger (feature-flagged); Dockerfile (Playwright base) + apprunner.yaml.

  • Phase 3 (ops) — ECR image pushed, DynamoDB table + S3 bucket + IAM roles live in us-east-1; Bedrock model access granted. The account-verification hold (friction log #9) was resolved 2026-10-14 via an AWS Support exception request — App Runner, CloudFront, and Bedrock standard-tier models are all live. Deployed: https://ba93fetekm.us-east-1.awsapprunner.com, images served over a real CloudFront distribution (dyswjdrhfb962.cloudfront.net). Confirmed live: unauthenticated POST /mcp → 401 with no WWW-Authenticate; discovery doc reports the correct issuer. Full authenticated round trip verified — real OAuth 2.1 PKCE login, real @modelcontextprotocol/sdk client, the whole sangeet sequence against the live service, found and fixed 3 real deploy-only bugs along the way (stale image missing plan_week, a RENDER_MODE cache-key mismatch, a missing s3:ListBucket IAM grant that broke compare_looks on any true cache miss — see docs/aws.md). Latency measured from a real us-east-1 CloudShell: suggest_outfits 90 ms (37–50 ms on repeat), preview_look (cache hit) 127–152 ms, compare_looks (cache hit) 123 ms — all well under the 500 ms budget. Phase 3 is fully done.

  • Phase 4 — Alexa+ add-on (alexa-ai deploy). Skipped: the add-ons console is partner-gated ("coming soon"), not self-serve. Pivoted to Phase 5 (Devpost's officially sanctioned simulated-experience path — see friction log #8).

  • Phase 5 — simulated Alexa+ web app (web/simulator.html): real OAuth 2.1 PKCE login + the real @modelcontextprotocol/sdk client (bundled for the browser, no CDN) over Streamable HTTP. The full sangeet flow runs end-to-end in a real browser: suggest_outfits → preview_look → save_look → preview_look (another option) → save_look → compare_looks, with real rendered images on the simulated Echo Show screen. Bedrock (the reasoning step that picks which tool to call) is still blocked account-wide (friction log #9), so src/web/ruleReasoner.ts — a small deterministic intent matcher — stands in automatically, tagged and visibly labeled in the transcript, never silent. It reverts to real Bedrock the moment the account clears, no config to flip back.

The full sangeet flow (npm run smoke) runs end-to-end from a cold seed: suggest_outfits → preview_look → save_look → compare_looks → record_reaction, every image tool a cache hit under 100 ms.

Deploy to AWS

STORE=dynamodb STORAGE=s3 TAGGING_PROVIDER=bedrock in us-east-1. Container from the Playwright base image (Dockerfile), run on App Runner (apprunner.yaml) with min instances = 1. Full steps + the single-table schema + IAM notes in docs/aws.md. One-time table setup: npm run aws:table.

See CLAUDE.md for the full plan and docs/ for the friction log, product feedback, and submission notes.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with Amazon services through AI assistants, allowing users to search products, manage their cart, view order history, and place orders using natural language.
    18 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables voice-first shared object memory for Alexa+, letting users record where items were last reported, retrieve authorized locations, correct stale records, check shared items in and out, run guided Lost Mode searches, and manage privacy-aware access.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI assistants and other MCP clients to query Amazon order history, individual orders, payment transactions, and gift card activity, and to manage sign-in, session, and site settings through natural-language tools.
    10
    1
    MIT