My vacation agent is struggling: how AI-ready are services?

My vacation agent is struggling: how AI-ready are services?

Anthony Viard

My wife and I wanted a few days on the French west coast in August, without the children. Two adults, a dog, somewhere with a spa, under €1,500, leaving around the 15th. Nothing exotic, except that we were booking it in the first week of August, because we always book late.

Comparing that properly across several sites is tedious, and I’m not good at it. Every site quotes prices differently, the filters never line up, and by the fourth tab I’ve forgotten what the second one offered. So instead of opening six tabs, I wrote a skill and let it do the comparing.

That decision turned into an accidental experiment. The first two providers I pointed the agent at had no API and no Model Context Protocol (MCP) server, so the only route in was a browser. The third had an MCP server I could connect to directly. Same skill, same criteria, same scoring, two completely different experiences for the agent. One of them also gave me a different answer, and the different answer was the correct one.

This is a testimony, not a benchmark. I ran it once, on one campaign, with one model, for my own holiday. But it turned into a useful lesson about how much the industry still has to change to work in a world where software does the shopping. The interesting part is not that MCP was faster. It’s that how ready a provider is for an AI reader changes what the agent can conclude, and readiness turned out to have surprisingly little to do with which protocol was on offer.

A note on naming: I’ve named every service I tested, because a testimony you can’t reproduce isn’t worth much. None of this is a verdict on those companies. It’s one run, on one campaign, on one day, and a site that frustrated my agent in July may have shipped a fix since. Read it as a snapshot of what an agent met, not a review.

The first thing I built was not a search. It was a reusable Agent Skill called vacation-scout, so the criteria and the scoring survived between sessions and I could re-run the same campaign later. A skill is a folder with a SKILL.md file, some frontmatter, and instructions:

---
name: vacation-scout
description: >
  Search configured travel providers for vacation offers matching a saved
  campaign (dates, budget, region, theme, pet policy), score every offer
  out of 100, and return one ranked table across all providers.
---

Its body defines four things: a campaign model, a per-provider search procedure, an extraction spec, and a scoring formula. That extraction spec matters more than it sounds. It lists the exact fields to pull per offer and includes one hard rule: if a field is not on the page, mark it "not specified" and never guess.

{
  "title": "4-star thalasso resort, Concarneau",
  "price_total_eur": 859,
  "nights": 4,
  "travelers": 2,
  "check_in": "2026-08-15",
  "location": "Concarneau, Finistère",
  "theme": ["spa", "thalasso"],
  "pets_allowed": "not specified",
  "breakfast_included": false,
  "url": "https://provider.example.com/offers/1a2b3c"
}

The scoring formula is deliberately boring, which is what makes it a fair constant across providers:

// Score one offer out of 100 against the saved campaign.
function scoreOffer(offer, campaign) {
  let score = 0;

  score += priceFit(offer.price_total_eur, campaign.budget_eur) * 30;
  score += themeMatch(offer.theme, campaign.theme) * 25;
  score += geoMatch(offer.location, campaign.regions) * 20;
  score += dateMatch(offer.check_in, campaign.window) * 15;
  score += durationMatch(offer.nights, campaign.nights) * 10;

  // Pet policy is a hard requirement, applied after the base total.
  if (offer.pets_allowed === true) score += 10;
  else if (offer.pets_allowed === false) score -= 20;
  else score -= 5; // "not specified" costs you, but less than a no

  return Math.max(0, Math.min(100, Math.round(score)));
}

That else score -= 5 line is the whole post, in a way. A missing field is not neutral. It’s a small, silent tax on the quality of the answer, and I’ll come back to it.

Here’s the campaign I saved:

Criterion Value
Departure 15 August 2026, flexible after
Duration 4 or 5 nights
Travelers 2 adults
Dog allowed Required
Region France, Atlantic or Channel coast
Theme Spa, thalasso, relaxation
Budget Under €1,500 total
Nice to have Breakfast included

Two providers, zero machine-readable entry points

Neither had a public API, and neither had an MCP server. So the agent drove a real browser: read the accessibility tree, click, screenshot, read again.

Staycation needed no account. It also turned out to sell 24-hour and 48-hour city getaways by default, and the multi-night option was hidden behind a secondary dropdown inside the date picker. The first two clicks on that dropdown closed the entire dialog. Coordinate-based clicks landed on the right pixels, and the handler still didn’t fire the way the agent expected. Switching to element-reference clicks after re-reading the accessibility tree fixed it. There was no pet filter anywhere on the results page, so confirming a pet policy meant opening each candidate’s detail page and reading the text. Four out of four candidates had no pet policy text at all. About 33 tool calls, 10 of them full-page screenshots.

Veepee had the better filters of the two, including an actual “pets accepted” facet, which felt like a win until combining it with the wellness facet behaved like an OR rather than an AND in the result count. Most of the pet-friendly coastal results were self-catering apartments with a seven-night minimum stay, and that minimum was invisible from the results grid. It only appeared after opening an individual listing, so the agent burned turns on inventory that could never satisfy a four-night trip. Narrowing to hotels collapsed the set to exactly one property: a real thalasso hotel, pets confirmed, and 39% over budget for August. About 32 tool calls, 19 of them screenshots.

Roughly 65 tool calls across the two. About 29 of them screenshots. One manual sign-in.

What the ranking looked like after the scrape

Rank Score Offer Provider Location Price, 4 nights, 2 people Dog allowed
1 95 3-star hotel and spa Staycation Morbihan, Atlantic €815 Not specified
2 95 4-star thalasso resort Veepee Finistère, Atlantic €2,084 Yes, confirmed
3 92 4-star thalasso hotel Staycation Île de Ré, Atlantic €1,267 Not specified
4 91 4-star hotel Staycation Saint-Malo, Channel €1,408 Not specified
5 80 4-star hotel Staycation La Baule, Atlantic €1,471 Not specified

Four of the five had no retrievable pet policy. I’m traveling with a dog. The single criterion that could disqualify an entire booking was unknown on most of the shortlist, and the agent had done nothing wrong: the information was not on the page.

Why the bill tracks turns, not information

I estimated the content-only token floor for the browser phase at around 60,000 input tokens and 4,300 output tokens. At the current Claude Sonnet 5 rate of $2 per million input tokens and $10 per million output, that’s about $0.16. Which sounds like nothing.

It is nothing, and it’s also not the bill. The Messages API is stateless, so every turn re-sends the full conversation: every prior screenshot, every prior page extraction, the system prompt, the skill text, and the schema of every loaded tool. For a general-purpose browser-automation agent, that fixed footprint is realistically somewhere in the 15,000 to 30,000 token range before you’ve done anything.

Take 20,000 as a working number:

Per provider (~33 turns):
  33 x 20,000  =   660,000 tokens of repetition  ->  ~$1.32
Both providers (~65 turns):
  65 x 20,000  = 1,300,000 tokens of repetition  ->  ~$2.60
Content floor for both:                              ~$0.16

With prompt caching active, cache reads bill at roughly 10% of the base input rate with periodic write refreshes, which pulls that figure closer to $0.65 to $0.85. Either way, the repetition dominates the actual information by a factor of four or more. A long browser loop costs what it costs because of how many times you go around, not because of how much you learn per lap.

One caveat on applying that same arithmetic to the MCP run: you can’t. The 20,000-token footprint is a general-purpose browser toolbox plus accumulated screenshots. lastminute.com’s session carried eight compact tool schemas and no images, so its per-turn footprint is a different and much smaller number that I did not measure. Fewer turns is the part I can count. A lighter turn is the part I can only argue. Either way, a narrower tool surface attacks both sides of that multiplication at once.

lastminute.com had an MCP server

The third provider published an MCP endpoint. That gave the agent a set of typed tools instead of a viewport.

Check the contract before the agent uses it

I did not wire the endpoint into the skill first. I opened it in Postman as an MCP request and looked at what it actually exposed, the same way I’d read an OpenAPI document before writing a client for it.

Create a new MCP request, point it at the server URL, and list the tools:

https://mcp.lastminute.com/mcp

The response tells you what the agent will be working with, including whether the fields you care about exist as real types:

{
  "tools": [
    {
      "name": "resolve_destination_id",
      "description": "Resolve a place name to a numeric destination ID",
      "inputSchema": {
        "type": "object",
        "properties": { "query": { "type": "string" } },
        "required": ["query"]
      }
    },
    {
      "name": "search_only_hotel",
      "description": "Search hotels by destination, dates, and occupancy",
      "inputSchema": {
        "type": "object",
        "properties": {
          "destination_id": { "type": "integer" },
          "check_in": { "type": "string", "format": "date" },
          "check_out": { "type": "string", "format": "date" },
          "adults": { "type": "integer" }
        },
        "required": ["destination_id", "check_in", "check_out", "adults"]
      }
    },
    {
      "name": "select_hotel_options",
      "description": "Room-level detail including a typed policies object"
    }
  ]
}

Two minutes of reading told me the thing I most needed to know: select_hotel_options returns a policies object with a pets_allowed boolean, which is a very different thing from a sentence about pets.

Running a few sample calls by hand before letting the agent loose is ordinary integration hygiene, and it paid for itself immediately. It also caught a formatting quirk in the price filter that would otherwise have surfaced mid-run. If you’re on the other side of this and publishing a server, the same checks apply from the MCP server docs before you ship one.

The same skill, 15 tool calls

Then I re-ran the identical campaign. Five coastal towns, the same 15 to 19 August dates, the same scoring function.

{
  "hotel_id": "fr-29900-0117",
  "name": "4-star thalasso resort",
  "price": { "total": 859, "currency": "EUR", "board": "room_only" },
  "stars": 4,
  "rating": 8.4,
  "distance_to_center_km": 1.2,
  "facilities": ["spa", "thalassotherapy", "indoor_pool", "parking"],
  "policies": {
    "pets_allowed": true,
    "check_in_from": "15:00",
    "cancellation": "free_until_2026-08-08"
  }
}

Side by side, per provider:

Metric Staycation Veepee lastminute.com (MCP)
Tool calls ~33 ~32 ~15
Screenshots ~10 ~19 0
Estimated input tokens, content floor ~27,000 ~33,000 ~10,300
Estimated output tokens ~2,000 ~2,300 ~2,500
Estimated cost, floor ~$0.07 ~$0.09 ~$0.05
Wall clock ~5 to 7 min ~5 to 7 min ~65 sec
Sign-in required No Yes, mid-run Yes, once at setup
Offers with a readable pet policy 0 of 4 1 of 1 Every offer queried

Per provider, the turn count drops from about 33 to about 15. That’s a bit better than half, not the four-fold cut the session totals suggest. The screenshot row is where the gap is not close: around 15 per browser provider, against zero.

Each browser provider took somewhere around five to seven minutes of clicking, reading, and waiting. The MCP data pull took about 65 seconds. I wasn’t running a stopwatch, so treat those as the rough shape of it rather than measurements.

On authentication, the fair comparison is where the sign-in sits, not whether there is one. The MCP connector did need authorizing, and I went through that flow like any other. But I did it once, before the run, and never touched it again. Veepee pulled me into the loop mid-search, and the agent sat idle waiting on a human. That’s the real difference: you pay setup friction once, and you pay interactive friction every time the agent runs.

Worth saying plainly in the other direction too: lastminute.com’s 15 calls covered five coastal towns, which is wider ground than either browser provider got in its ~33. So the per-provider row understates the gap somewhat. I’d rather point that out than quietly bank it.

The part I did not expect: the answer changed

I went in expecting cheaper and faster. I got a different result, and that’s the finding I keep thinking about.

Running the detail call on the properties that mapped to the browser-phase winners rewrote the top of the ranking:

  • The browser phase’s number one, scored 95 with pets “not specified,” came back pets_allowed: false. Under the same formula, that flips a minus-5 into a minus-20 and drops it to 80. My top recommendation was disqualifying for a dog owner, and scraping never surfaced that, because the page never said it.
  • The browser phase’s number three, also “not specified,” came back pets_allowed: true. A clean upgrade from a guess to a fact.

Structured search returned fields the point-and-click pass could not read, on the one criterion that decided the booking.

Cheaper and faster would have been a nice footnote. Being right about the dog is the part that actually changed where we slept.

What this test is not

Setting expectations honestly, because I’d want someone to do the same for me:

  • It’s one campaign, run once. No repeated trials, no confidence intervals, nothing controlled.
  • The token and cost figures are estimates built from tool-call counts and typical per-call sizes, not exact metering. Pull the real numbers from your own usage dashboard before quoting any of them.
  • These three are not equivalent businesses with equivalent inventory. A flash-sale club and a global aggregator stock different rooms, so some of the difference in results is catalog, not protocol.
  • I never ran the campaign against an ordinary REST API, which is the tier most services occupy. Every travel API I looked at needed a partner agreement first, so that comparison is still missing.
  • Two browser providers and one MCP provider is a small sample. The per-provider table is the fair read, the session totals are not, and one MCP server is no evidence that every MCP server behaves this way.
  • The browser-driving tooling is a research preview and behaved like one, including a mid-session disconnect.

None of those caveats move the part that mattered to me. Four of five offers with an unknowable pet policy is not a measurement artifact. That’s what a webpage had to say on the subject.

Agent experience is the thing, not the protocol

The tempting conclusion is “ship an MCP server.” That’s not quite it, and it lets too many teams off the hook.

What actually changed between the two phases was how much work the agent had to do to reach a fact, and how confident it could be once it got there. A boolean beats a paragraph. A typed price with a board field beats a number on a card that might be per person, per room, or per stay. A filter the server applies beats a filter the agent has to re-verify. MCP is one good way to deliver that, and now that the protocol is stateless it deploys like any ordinary HTTP service, which removes most of the operational excuse for not having one.

It’s not the only way either, and I’d be overselling one afternoon’s experiment if I claimed otherwise. A REST API with an accurate OpenAPI document and honestly typed fields hands an agent exactly the same thing. So does a published SDK. So does a command-line tool with predictable output and real exit codes. What my agent needed was a value it could act on. Whether that arrives over MCP, over HTTP, or out of a binary matters far less than whether it arrives as a type or as a sentence somebody has to interpret. I’d expect lastminute.com is reading that boolean out of their own database rather than inventing it at the protocol layer.

So Staycation and Veepee didn’t lose because they lacked MCP. They lost because there was no machine-readable path to their inventory in any form, so the only reader they had ever designed for was a human with a mouse.

That splits into two questions worth keeping separate. Can an agent find and reach your data at all, and once it has, can it tell what it’s holding? The first is a discovery problem, and almost anything machine-readable solves it: a specification, a collection, an SDK, a command-line tool, a server. The second is a modelling problem, and no protocol fixes it for you. You can ship a well-built API and still answer “does this hotel take dogs” with a paragraph of prose.

Some of the traffic reading your API now is software working on somebody’s behalf, and it can’t infer what you meant from a page layout or a helpfully worded sentence.

Score your own API

If you want to know how legible your APIs are to an agent before someone else finds out for you, Postman published an assessment for exactly this: the AI-Ready APIs score. It grades how well agents like Claude, ChatGPT, and Cursor can find, read, and build with what you publish across your specifications, docs, collections, SDKs, and MCP servers, and there’s a public leaderboard.

Note what it doesn’t do. It doesn’t ask whether you shipped an MCP server. It asks whether an agent can find you, read you, and act on you without a human standing in the middle. Which is all my agent ever wanted, on its way to finding us a hotel.

Run the assessment on your own organization and read the low scores as a to-do list, not a grade. Missing types, undocumented enums, and fields that exist in the response but not in the specification are the exact things that turn into “not specified” in someone’s agent output. If your Agent Understanding row is the weak one, the fix is usually cheap: an AGENTS.md, an llms.txt, a public Postman Collection, and structured data on the docs pages.

I got a better hotel out of this, which was the entire point. Four nights on the Finistère coast, thalasso spa on site, in budget, and a dog who is welcome in writing rather than on a hunch.

What I also got was a much clearer sense of which providers had thought about who, or what, would be reading them.

Resources

What do you think about this topic? Tell us in a comment below.

Comment

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.