Inspiration

A barber with clippers in hand can't answer the phone. A stylist halfway through a colour can't reply to messages. Every missed call is a missed booking, and in one phone study of 142 UK small businesses, 47% missed the first call. Most independent businesses in Johannesburg, where I live, still take bookings exactly that way.

AI assistants like Alexa+ can now act for customers. But a booking is a promise. An assistant that invents a free slot, books a service the owner only does after a consultation, or books without a clear "yes" costs a small business money and trust. I wanted the assistant to be the friendly front desk, and something reliable to be the counter.

What it does

For customers: say "a haircut on Tuesday afternoon, I'm Thandi". Ozza, the corgi guide, offers only times that are really free in the owner's calendar, reads the booking back word for word, and books nothing until the customer says yes. The customer gets a receipt of every rule the server checked and a six-character code they can say out loud to cancel later.

For owners: set up in one spoken conversation, the way you'd brief a new receptionist: services, prices, hours, and rules like "never book colour by voice". Connect Google Calendar with one click. Get a phone alert for every booking, from the voice page, the booking page or any AI assistant.

For AI assistants (Alexa+ and any MCP client): one MCP server for every listed business (/mcp, starting with find_business), tools written to be spoken aloud, and an MCP Apps booking card for screens like Echo Show.

How I built it

The AI understands; the counter decides. A deterministic parser goes first, and a language model may only fill gaps; it can never invent a day or time. Every decision that matters happens in code, in eight checks on every booking:

  1. The business is accepting bookings
  2. The service is allowed by voice
  3. The time is inside opening hours
  4. Notice and gaps between appointments are respected
  5. The calendar is free, re-checked while the slot is held
  6. The slot is locked in one atomic database statement, so ten simultaneous requests produce exactly one booking (tested)
  7. The booking matches a read-back the server itself signed, bound to the business, service, time and name, so an assistant cannot skip the read-back or book something different
  8. The event is written to the calendar idempotently

A refusal names the rule that failed (failedCheck) and, when a day is closed or full, offers the next free time. Nothing is ever answered with an empty list.

Stack: MCP TypeScript SDK (spec 2025-11-25, Streamable HTTP, stateless) on Cloudflare Workers, D1 and Workers AI (Whisper for speech, a Llama model for gap-filling), Google Calendar API with OAuth, Web Push (RFC 8291/8292) written on WebCrypto with no library, React for the web app, and a Strands Agents concierge in Python. All of it runs free with no card.

Voice-first tool design: every tool has a title, annotations, an output schema and a summary written to be said aloud. Times read as "Tuesday 20 October at 3 pm", never as timestamps. Booking codes are spelled out: "Z H 7, X R 2".

Tests: 84 automated tests run the real Worker code against SQLite standing in for D1. They cover time zones, concurrency, forged and expired read-back tokens, prompt injection in calendar titles, isiZulu and Afrikaans day words, and Web Push encryption checked by an independent decryptor. The deploy script refuses to ship if one fails.

Challenges I ran into

  • A small model invented availability. I moved every decision into code; the model only understands.
  • "Confirmed" can't just be a flag. My first version trusted customerConfirmed: true. Now the server signs each read-back and books only those exact details. The human "yes" stays the assistant's job, and my Strands client enforces it with a hook.
  • I couldn't deploy to Alexa+ itself. The Alexa AI CLI needs an AWS account (which needs a card) and the MCP Toolkit is US-only. So I built to the documented requirements, prepared the add-on listing, and built a simulated Alexa+ experience: a voice page that speaks to the same MCP server.
  • The 500 ms budget against a remote calendar. A first, uncached Google Calendar read from South Africa takes 0.5 to 1.3 s. A 30-second cache brings repeat calls to about 35 ms, and booking never trusts the cache. The first call can still miss the budget, and I say so.

Accomplishments that I'm proud of

  • A real booking flow from voice to the owner's Google Calendar to a notification on the owner's iPhone.
  • One add-on for every business, instead of one per barber.
  • A receipt that makes the AI's safety visible to customers.
  • Trying to break it, and watching it refuse: blocked services, closed days, skipped read-backs, ten assistants racing for one slot.

What I learned

For actions that cost real money, the model should propose and the server should decide, and customers trust what they can see checked.

What's next for Open Counter

Staff and chairs, WhatsApp (the channel South African customers already use), deposits to cut no-shows, SMS reminders, and a live Alexa+ add-on as soon as the toolkit is open to me.

Honest status

The MCP server meets the documented Alexa+ requirements I could find, but it has not run on Alexa+ itself. The voice page is the simulated Alexa+ experience. The Strands concierge ran end to end with Gemini; the Amazon Bedrock path is in the code but was not run, because I have no AWS account. The demo film's voice-over is my own voice, cloned with Chatterbox (open source).

Built With

Share this project:

Updates

Submission history