Skip to content

Tisankan-dev/kapu

Repository files navigation

Kapu

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.

What you get

  • 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_cities aliases, 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.

Stack

  • 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):
    1. Google Gemini 2.0 Flash (primary) — 1500 req/day free, native Sinhala/Tamil
    2. Groq Llama 3.3 70B versatile (fallback) — sub-second latency
    3. Groq Llama 3.1 8B instant (last resort) — smaller, faster
  • Custom Streamable-HTTP MCP client (no external SDK — keeps the install small)
  • lucide-react for icons

Free-tier guarantee — you will not be billed

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:

  1. attach billing at the provider, AND
  2. set KAPU_ALLOW_PAID_MODELS=true in .env.local, AND
  3. set GEMINI_MODEL= or GROQ_MODEL= to a paid model name.

You will never be billed by accident.

Run locally

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

Deploy to Vercel (free, zero config)

# from this directory
npx vercel link    # link to your Vercel project
npx vercel env add GROQ_API_KEY production
npx vercel --prod

Bind the custom subdomain kapu.tisankan.dev in Vercel: Settings -> Domains -> Add. Then create a CNAME on your DNS to the Vercel target.

Project layout

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

How the agent works (one paragraph)

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.

Language

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.

Cost / reliability

  • 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.

Demo paths

  • 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 Sunday into the composer.
  • Sinhala script path: සිංහල නව අවුරුද්දට අම්මාට මල් කළඹක් යවන්න ඕනේ.
  • Tracking path: paste a real Kapruka order number from the confirmation email.

License / credits

Built by Tisankan Jeyakumar for the Kapruka Agent Challenge 2026. MCP integration uses the public Kapruka MCP at https://mcp.kapruka.com/mcp.

Releases

No releases published

Packages

 
 
 

Contributors

Languages