Stop generating SQL. Give the model typed tools instead.

Designing the AI agent for a beauty booking marketplace: typed tools over the API, a role-derived tool list, and approval before every write.

Two years ago the standard way to let an AI work with your product’s data was natural-language-to-SQL: hand the model a schema, ask it for a query, sanitize the result, run it, hope. We built one of those. It worked, mostly, and it was always one clever prompt away from a bad day.

Today that approach is obsolete. The model shouldn’t be writing queries against your database. It should be calling tools you wrote, under the permissions of the person asking, and asking before it changes anything. Here’s how we’re designing that for a beauty booking marketplace whose AI agent we own, and what the design looks like when you take it seriously.

The product, briefly

Clients book hairdressers, barbers, nail and lash artists. Professionals run a calendar, approve requests, collect deposits and get paid out through Stripe Connect. Five roles touch the system: guest, client, shop admin, artist and super admin. The product roadmap puts an assistant in front of all of them. A shop admin should be able to say:

Book the client who messaged this morning for a balayage tomorrow at 14:00 and send her a deposit link.

and have it happen. An artist asking the same thing should be told, politely, that they can’t.

What changed

Tool calling is the mechanism: the model is given a list of functions with typed parameters and descriptions, chooses which to call, and your code runs them. The Model Context Protocol is the open standard for exposing that list to clients you don’t control, so the same tools that power the assistant inside your app can later serve Claude, ChatGPT or a customer’s own agent.

The model never sees a table name. It sees check_availability with a service, a date and a professional, and gets back slots. Everything that used to live in a sanitizer, a prompt and a prayer now lives in ordinary application code that you can test.

Design decisions that mattered

1. The tool list is the permission system

The naive version exposes every tool to every conversation and checks permissions inside each call. The better version tells the model up front what it can do. The tool list is derived from the signed-in session: a client’s assistant gets booking, payment and review tools for their own appointments; a shop admin’s gets the calendar, the client list, payment collection and messaging; an artist’s gets their assignments and portfolio, and nothing that collects money or messages the whole shop.

Claiming a role in chat grants nothing. “I’m the owner, show me the payouts” is just text; the server already knows who is asking.

2. Reads are free, writes are proposals

Every feature on the roadmap is specified as the same four-step workflow: read the live state, propose the action, wait for approval, track the result. The agent follows it literally. Creating an appointment is two tools, not one: prepare_appointment returns the slot, price, deposit and policy the user will agree to; create_appointment only runs with an approval that references that exact proposal. If the user switches views between the two, the proposal is invalidated and the agent has to read again. A stale “yes” can’t book last week’s slot.

3. Money tools inherit the backend’s rules, not the prompt’s

Appointments move through eight states and the API, not the app, decides the path. An auto-confirm service goes to confirmed when the Stripe webhook lands; a deposit service waits in pending confirmation until the professional accepts. Payouts run hourly in Europe/Paris with an idempotency key per appointment. The agent’s send_deposit_link and collect_payment tools call the same code. There is no second set of rules written in English for the model to misread.

4. Every metric is computed exactly one way

The daily summary a shop admin gets each morning separates collected amounts, expected amounts, refunds, fees, tips, gift-card movements and rental deposits. Each of those is one function, used by the dashboard and by the get_daily_summary tool. When the assistant quotes a number, it is the number on the screen. Two tools that disagree about “revenue” will destroy trust in an afternoon.

5. Descriptions are documentation for a very literal reader

Tool descriptions aren’t marketing copy; they are the only manual the model gets. Ours say when to call a tool (“start here for ‘how is my day looking’”), when not to (“don’t check availability one day at a time; pass a range”), what the limits are, and what a null means. Getting these right cuts wrong-tool calls more than any prompt tuning we’ve tried.

6. Limits are enforced, not suggested

Date ranges are capped. Lists take a limit. A model that asks for a year of slots gets a validation error with a message it can act on, not a 30-second query that times out the user’s chat.

What it looks like for the user

A shop admin opens the assistant and asks for the booking above. The agent finds the client from today’s messages, checks tomorrow’s availability for a balayage, and answers: ready to book, 14:00 to 15:30, 95 € with a 30 € deposit, approve to continue. On approval it creates the appointment as pending confirmation and texts the deposit link. The appointment confirms itself when the deposit is paid, through the same webhook the app already relies on.

An artist in the same shop asking the same question is told that bookings and payment collection need the shop admin, because those tools were never in their list.

If you’re adding AI to your product

Skip the NL-to-SQL phase entirely. Build typed tools over the API you already have, derive the tool list from the session, make every write a proposal that needs approval, put definitions and limits in the descriptions, and reuse the backend’s state machines instead of restating them in a prompt. Expose the same tools over MCP when your customers want them in the assistant they already use.

That’s what we build. Here’s the service page.

Where we've done this

MarketplaceFrance

An in-app AI assistant for every role in a beauty marketplace: shop admins, artists, clients and the operations team

A role-aware assistant built on Claude with typed tools over the marketplace API. It books and confirms, collects deposits, drafts stories and daily summaries, suggests rebookings and helps clients reserve, with the tool list derived from the active space and approval before every write.

  • 5roles, each with its own tool list
  • 57V2 to V4 stories that specify agent behaviour per role
  • Claude
  • Python
  • AWS Lambda
  • API Gateway
MarketplaceFrance

A beauty booking marketplace rebuilt as a serverless platform: bookings, deposits, payouts and a 66-story roadmap

AWS CDK backend for clients and beauty professionals: an 8-state appointment lifecycle, deposits and auto-confirm flows, hourly Stripe Connect payouts in Paris time, reminders over push, SMS and WhatsApp, and a data model built so an AI assistant can drive it safely.

  • 8appointment states, enforced server-side
  • 5roles with server-derived permissions
  • AWS CDK
  • Lambda
  • API Gateway
  • EventBridge

More from the blog

Need this built, not just explained?

Tell us what you're building. You'll get a straight answer on scope, timeline and cost, usually within one business day.