Note on the name: this is the Orbixio product. It is not the 2024 tour booking platform I built as my honours project at Glasgow Caledonian University, which shares the name and nothing else.
Context
Planning a trip means opening ten tabs — a flights site, two hotel sites, a car-hire comparator, a tours marketplace — and then reconciling dates, budgets and logistics in your head. The obvious use for a language model here is the reconciliation. The non-obvious part is that almost nobody does it properly, because giving a model real inventory is much harder than letting it improvise.
The generic AI trip planner produces a beautiful itinerary describing flights that do not exist at prices that were never real. It demos well and it is worthless. Tourista exists to be the other thing.
The problem
Three problems, and only the first one is about AI:
- Grounding. The model must search live inventory rather than recall it. That means typed tools, real API calls, and results the model has to work with rather than around.
- Latency you cannot hide. Resolving a place, then searching flights, stays, cars and tours, then assembling and costing the result takes tens of seconds. A spinner for that long is an abandoned session.
- The regulatory line. Selling a flight and a hotel together as one product in the UK engages ATOL and the Package Travel Regulations. Building the booking flow before understanding that line is how a side project becomes a legal problem.
What I built
A three-package system with real boundaries
frontend/ React 19 + Vite + TypeScript + Tailwind + Framer Motion (SPA) backend/ Node + Express + TypeScript + MongoDB/Mongoose (REST + Socket.io) duffel-mcp/ Model Context Protocol server exposing the travel tool layer
Five typed tools, one agent loop
A manual Claude tool-use loop drives five typed tools: resolve_place,
search_flights, search_stays, search_cars and
search_tourista_tours. The first four hit the Duffel API; the fifth queries Tourista's
own curated products in MongoDB, so the platform's own inventory competes on equal footing with
third-party supply. The loop is bounded — a tool-calling agent without a call ceiling is an
unbounded bill.
Streaming, so the wait becomes the interface
Results stream to the browser as NDJSON over a single response: text deltas plus live tool-progress events. The itinerary types out as it is written, and each tool call surfaces in the UI as it happens — "Searching flights MAN→BCN". The thirty-to-sixty-second blank spinner became a visible process. Nothing about the underlying latency changed; everything about the experience of it did.
One tool layer, two consumers
The Duffel integration is written once and consumed twice — by the web backend, and by a standalone MCP server that exposes the same tools to Claude Desktop. The same travel tools work inside the product and inside any MCP client, with no duplicated logic and no drift between them. This is the piece of the architecture I am most confident about: it treats tool logic and transport as separate concerns, which is exactly what MCP is for.
The rest of a real product
JWT auth, a tours catalogue, bookings, reviews, Stripe checkout via PaymentIntents, Cloudinary media, Socket.io, Helmet, CORS and rate limiting, Winston logging and central error handling. The AI planner is the interesting part; it is sitting on a product that works without it.
A demo anyone can run
npm run demo boots an ephemeral in-memory MongoDB, starts the real API, and seeds users
and eight rich tours. No Atlas account, no Docker, no cloud keys. A reviewer with Node installed sees
the whole stack running in one command.
A motion system rather than scattered animation
A shared motion module defines easings and variants, and nine reusable animated components — Aurora,
Marquee, TextReveal, MagneticButton, Spotlight, BentoGrid, CountUp, Skeleton, ScrollProgress — keep
every page consistent. All of it respects prefers-reduced-motion.
Decisions I would defend
A manual tool-use loop instead of an agent framework
Frameworks are excellent until the day the behaviour you need is one the abstraction does not expose — and with a streaming, bounded, tool-progress-emitting loop, that day arrives immediately. I wrote the loop. It is a few hundred lines, I can reason about every branch of it, and the retry, ceiling and error-surfacing behaviour is mine rather than inherited.
Streaming NDJSON rather than WebSockets for the planner
The planner is one request producing one growing response. That is what a streaming HTTP response is. Socket.io is already in the stack for other features, so reaching for it here would have been free in effort and wrong in shape — a persistent bidirectional channel for a unidirectional request/response is complexity with no return.
Search-and-assemble, not merchant of record
Tourista searches and assembles; it does not sell a packaged holiday. That is a deliberate product boundary drawn around the UK ATOL and Package Travel Regulations line, decided before the booking flow was built rather than after. The affiliate-referral route to revenue exists precisely because it keeps the product on the right side of that line.
Giving the model tools rather than context
The alternative design — fetch a pile of options, stuff them into the prompt, ask for an itinerary — is simpler and fails in a specific way: the model can only work with what you guessed it would need. Tools let it ask its own follow-up questions. It costs more round trips and produces a system that holds up when the request is unusual.
The result
- A production-shaped MVP: auth, catalogue, bookings, payments and an AI planner working end to end against real travel inventory.
- Clean TypeScript across all packages and passing production builds.
- Runnable by anyone in one command, seeded with eight tours and login accounts.
- A reusable MCP travel tool layer that works in Claude Desktop as well as in the product.
What I would do next
Deploy behind a domain (Vercel, Render, MongoDB Atlas); add an affiliate-link mode via Travelpayouts to monetise referrals without merchant-of-record obligations; and narrow to a defensible niche instead of competing with everyone at once.
My role and stack
Role: Solo full-stack engineer and systems designer — architecture, backend, frontend, AI agent, developer experience. Built under Orbixio Ltd, where I am Founder.
Frontend: React 19, TypeScript, Vite, Tailwind CSS, Framer Motion, Zustand, React
Router, Stripe Elements.
Backend: Node.js, Express, TypeScript, MongoDB, Mongoose, JWT, Socket.io, Stripe,
Cloudinary, Helmet, Winston.
AI and integrations: Anthropic Claude (tool-use and streaming), Duffel
flights/stays/cars, Model Context Protocol.
Tooling: mongodb-memory-server, ESLint, tsx.