A Sinhala / Tamil / Tanglish / English gifting concierge built on the public Kapruka MCP, for the Kapruka Agent Challenge 2026.
Kapu turns "I am not sure what to send" into a paid, delivery-checked gift in a few taps — and renders everything as a living visual storefront (carousels, cards, a delivery planner, an animated cart, a shareable gift-reveal card) instead of a wall of chat text.
- Full-screen chat surface, mobile-first, calm premium motion.
- Typed UI directive protocol — the model is constrained to emit valid directives, the renderer is dumb and fast.
- All 7 Kapruka MCP tools wired server-side (no auth, public):
search_products,get_product,list_categories,list_delivery_cities,check_delivery,create_order,track_order. - Language engine: per-message Sinhala / Tamil / Tanglish / English detection, Tanglish search normalisation, vernacular city resolution via
list_delivery_citiesaliases, reply-in-kind. - Multi-item cart (localStorage), delivery-date constraints with perishable nudge, gift-message editor, real Kapruka guest-checkout pay link, order tracking.
- Resilience: 30-minute read cache, rate-limit-aware queue with pre-emptive backoff, idempotency guard on
create_order. - The audacious flex: a client-side SVG -> PNG gift-reveal share card.
- Voice-in via the browser-native Web Speech API (Chrome / Edge / Safari macOS).
Total monthly cost: LKR 0. Hosting on Vercel Hobby; LLM on Groq free tier; MCP is public; share card is client-rendered.
- Next.js 15 (App Router) + React 18 + TypeScript + Tailwind CSS
- Three-tier LLM with automatic fallover (each provider is a separate rate-limit bucket — three layers of judging-day headroom):
- Google Gemini 2.0 Flash (primary) — 1500 req/day free, native Sinhala/Tamil
- Groq Llama 3.3 70B versatile (fallback) — sub-second latency
- Groq Llama 3.1 8B instant (last resort) — smaller, faster
- Custom Streamable-HTTP MCP client (no external SDK — keeps the install small)
lucide-reactfor icons
Every model Kapu calls is on the provider's PUBLIC FREE TIER.
- Google AI Studio (Gemini Flash) — free tier; if you exhaust the daily quota the API returns HTTP 429 and Kapu fails over to Groq. Google AI Studio does not auto-attach a billing account; you have to explicitly create and link one in Google Cloud Console before any paid request can happen.
- Groq — free tier; same story. 429 on exhaustion, no auto-billing.
In addition, the code has a hard allow-list (FREE_TIER_MODELS in src/lib/agent/llm.ts) that blocks any request to a non-free model name (Gemini Pro, Imagen, Veo, Lyria, Nano Banana, Deep Research, etc.) BEFORE the HTTP call goes out. To override you would have to:
- attach billing at the provider, AND
- set
KAPU_ALLOW_PAID_MODELS=truein.env.local, AND - set
GEMINI_MODEL=orGROQ_MODEL=to a paid model name.
You will never be billed by accident.
cp .env.example .env.local
# add your Gemini key from https://aistudio.google.com/apikey
# optionally also add a Groq key from https://console.groq.com/keys (fallback)
npm install
npm run dev
# open http://localhost:3000# from this directory
npx vercel link # link to your Vercel project
npx vercel env add GROQ_API_KEY production
npx vercel --prodBind the custom subdomain kapu.tisankan.dev in Vercel: Settings -> Domains -> Add. Then create a CNAME on your DNS to the Vercel target.
src/
app/
layout.tsx root layout + metadata
page.tsx -> KapuApp (full-screen chat)
globals.css base styles + shimmer + reduced-motion
api/chat/route.ts the single chat endpoint
components/
kapu-app.tsx the shell: header, scroller, composer, cart drawer
welcome-hero.tsx the cold-start hero + chip starters
chat-composer.tsx message input + voice mic
message-bubble.tsx user + assistant bubble framing
skeletons.tsx shimmer placeholders + thinking dots
use-cart.ts cart store (localStorage)
use-session.ts recipient / delivery / sender memory
directives/
renderer.tsx dispatch table
product-card.tsx single card
product-carousel.tsx horizontal carousel of cards
product-detail.tsx detail with variants + gallery
cart-summary.tsx live cart (LKR total, animated)
delivery-planner.tsx city + date + result
gift-message-editor.tsx textarea + char count
checkout-card.tsx the real Kapruka pay link
order-tracker.tsx status + progress timeline
quick-replies.tsx chip row
gift-reveal.tsx the audacious flex (SVG -> PNG)
lib/
ui-protocol.ts typed Directive union
format.ts LKR + date helpers
language.ts detect + normalise Tanglish/Sinhala/Tamil
mcp/
types.ts raw MCP shapes (from real recon)
client.ts streamable-HTTP transport + cache + rate-limit
tools.ts typed wrappers around the 7 tools
agent/
groq.ts tiny Groq chat-completions client
prompt.ts system prompt builder
tool-schemas.ts OpenAI-style tool schemas (7 MCP + helpers)
loop.ts two-pass tool loop -> directive JSON
docs/
mcp-shapes.md real captured Kapruka MCP responses
kapu-prd.md the original product brief
Every user message hits POST /api/chat. The route runs the agent loop in two passes. Pass A is the tool pass — the model can call any of the 7 Kapruka tools (up to 4 rounds), and we pre-fire a search_products based on the normalised search hint so the model always has real data in context before it paints. Pass B is the directive pass — the model is forced to JSON with response_format=json_object and outputs one typed UI directive object. The browser dispatches each directive to its purpose-built component. The cart and session memory live in localStorage and are sent in every request so the model can be precise at checkout.
src/lib/language.ts detects per message and the prompt instructs reply-in-kind. The normaliser maps amma / mal / kalu / Kandy ekata and Sinhala/Tamil words into clean English search tokens BEFORE the search step, so Tanglish queries still get relevant results. The model itself replies in the user's language — including code-switching.
- Read cache mirrors the MCP server's 30-min server-side cache. Same tool + args = no second HTTP round trip.
- Pre-emptive backoff: if the server header
ratelimit-remaining <= 2, the client sleeps until the window resets. - Idempotency guard on
create_order: identical cart + recipient + delivery + sender returns the cached checkout URL for an hour — no double-orders on retries. - 429 from the server triggers one polite retry after the server's stated reset.
- LLM key is server-side only. The browser never sees it.
- Cold open -> tap "Birthday" chip -> chocolate carousel -> add to cart -> "Plan delivery" chip -> Colombo + date -> "Add a card" -> gift message editor -> "I am ready to checkout" -> real Kapruka pay link.
- Tanglish path: paste
amma ta surprise ekak ona, Kandy ekata Sundayinto the composer. - Sinhala script path:
සිංහල නව අවුරුද්දට අම්මාට මල් කළඹක් යවන්න ඕනේ. - Tracking path: paste a real Kapruka order number from the confirmation email.
Built by Tisankan Jeyakumar for the Kapruka Agent Challenge 2026. MCP integration uses the public Kapruka MCP at https://mcp.kapruka.com/mcp.