---
title: "AI Travel Planning Agent: Flights and Hotels via MCP"
description: "Chain Google Flights and hotel search through MCP tools in one agent. The exact tool calls, a runnable script and the 6 credits one trip plan costs."
url: https://perceptlayer.com/blog/ai-travel-planning-agent-mcp
---

[Blog](https://perceptlayer.com/blog) / [AI agents](https://perceptlayer.com/blog/category/ai-agents)

# AI Travel Planning Agent: Flights and Hotels via MCP

20 Sept 2026 Perceptlayer 8 min read

An AI travel planning agent needs two things: live prices and a way to call them. Connect an agent to the Perceptlayer MCP server and it can search flights and hotels with three tools, `search_endpoints`, `get_endpoint` and `call_endpoint`. One trip plan in this tutorial takes six paid calls, so six credits. The agent reads public prices and never books anything.

## Key takeaways

-   One MCP server, `https://api.perceptlayer.com/mcp`, exposes the Google Flights, Google Hotels and Booking.com endpoints through three tools.
-   `search_endpoints` and `get_endpoint` cost nothing; each `call_endpoint` that succeeds costs 1 credit for every endpoint used here.
-   The plan below uses six paid calls: one date-range search, two one-way flight searches, two hotel searches and one reviews lookup. Total: 6 credits.
-   The free tier of 1,000 credits a month covers 166 whole trip plans (1,000 ÷ 6 = 166.67).
-   The agent only reads prices. Booking is left to the traveller, and the prompt says so.

## How does an agent plan a trip with MCP tools?

The agent follows a short dependency chain. It finds the flight and hotel endpoints in the catalogue, reads their parameters, then calls them in order: a date search narrows the departure day, flight searches price the legs, and hotel searches price the stay. Each step feeds the next, which is why tool calling suits this job better than one big request.

[MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) is the protocol that lets an agent discover and call tools. This server speaks Streamable HTTP, so any client that supports remote MCP servers can connect. For the wider picture, see [MCP vs function calling for live web data](https://perceptlayer.com/blog/mcp-vs-function-calling-web-data) and [how an MCP server for web data works](https://perceptlayer.com/blog/mcp-server-for-web-scraping). If you prefer a framework, [a LangChain custom tool](https://perceptlayer.com/blog/langchain-custom-tool-web-data) can wrap the same endpoints, and [Claude MCP web search](https://perceptlayer.com/blog/claude-agent-live-search-mcp) covers a single-tool agent.

Here is the chain this tutorial builds. The credits column counts only successful calls.

| Step | Tool | Endpoint | Credits |
| --- | --- | --- | --- |
| 1 | `search_endpoints` | Catalogue lookup | 0 |
| 2 | `get_endpoint` | Catalogue lookup | 0 |
| 3 | `call_endpoint` | `/v1/google-flights/date-range` | 1 |
| 4 | `call_endpoint` | `/v1/google-flights/one-way` (outbound) | 1 |
| 5 | `call_endpoint` | `/v1/google-flights/one-way` (return) | 1 |
| 6 | `call_endpoint` | `/v1/google-hotels/search` | 1 |
| 7 | `call_endpoint` | `/v1/booking/search` | 1 |
| 8 | `call_endpoint` | `/v1/google-hotels/reviews` | 1 |
|  |  | Total | 6 |

## What do you need before you start?

You need an API key and an MCP client. The key is free: verify an email address and you get 1,000 free credits every month, with no credit card. Keys start with `sk_live_`. Claude Code is the client used here because it adds a remote server in one command, but any MCP client that supports Streamable HTTP works the same way.

### Step 1: get a key

[Get a free API key](https://perceptlayer.com/signup), then export it. The [authentication docs](https://docs.perceptlayer.com/authentication) cover the `X-API-Key` header that the plain HTTP calls in step 5 use.

```bash
export API_KEY=sk_live_your_key_here
```

### Step 2: add the MCP server

Run this once. It registers the server under the name `perceptlayer` and passes your key as a bearer token, as described in the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) and the [AI agents section of the docs](https://docs.perceptlayer.com/ai-agents).

```bash
claude mcp add --transport http perceptlayer https://api.perceptlayer.com/mcp \
  --header "Authorization: Bearer $API_KEY"
```

## Which endpoints does the agent chain together?

The agent draws on five endpoints from three APIs, and a single plan makes six paid calls because the one-way search runs twice. Flights come from the [Google Flights API](https://perceptlayer.com/apis/google-flights), hotel prices and ratings from the [Google Hotels API](https://perceptlayer.com/apis/google-hotels), and a second hotel price list from the [Booking.com API](https://perceptlayer.com/apis/booking). All of them cost 1 credit per successful call.

| Job | Endpoint | Required parameters |
| --- | --- | --- |
| Find a cheap departure day | `GET /v1/google-flights/date-range` | `origin`, `destination`, `from_date`, `to_date` |
| Price one leg | `GET /v1/google-flights/one-way` | `origin`, `destination`, `departure_date` |
| Search hotels | `GET /v1/google-hotels/search` | `q`, `check_in_date`, `check_out_date` |
| Cross-check hotel prices | `GET /v1/booking/search` | `ss` |
| Read reviews for one property | `GET /v1/google-hotels/reviews` | `property_token` |

Two details make the chain work. The flight response carries a `booking_token` per fare, and the hotel response carries a `property_token` per property. The reviews call needs that `property_token`, so the hotel search has to come first. Every parameter above is documented on its endpoint page, for example [One Way](https://perceptlayer.com/apis/google-flights/one-way) and [Google Hotels Search](https://perceptlayer.com/apis/google-hotels/search). For each API on its own, see the articles on the [Google Flights API](https://perceptlayer.com/blog/google-flights-api) and the [Google Hotels API](https://perceptlayer.com/blog/google-hotels-api).

## How do you prompt the agent so it stays inside budget?

Put the limits in the prompt: what to search, how many paid calls it may make, and what it must not do. A prompt is an instruction, not a guarantee, so treat the cap as guidance and the prepaid balance as the hard stop. When the balance reaches zero, calls return `insufficient_credits` (402) and nothing more is debited.

### Step 3: write the prompt

Save this as `trip-prompt.txt`.

```text
Plan a 3-night trip from JFK to Los Angeles, leaving around 15 December 2026 and returning 18 December 2026. One adult.

Use the perceptlayer MCP tools. Look up endpoints with search_endpoints and read their parameters with get_endpoint before you call anything.

Make at most 6 call_endpoint calls in total:
1. A date-range flight search to check the departure window.
2. A one-way search for the outbound leg and one for the return leg.
3. A Google Hotels search for Los Angeles for the same dates.
4. A Booking.com search for the same dates, to compare prices.
5. A Google Hotels reviews lookup for the single hotel you recommend.

Report the cheapest fares, the three best-value hotels and the credits used and remaining, as call_endpoint reports them after each call.

Do not book, pay for or reserve anything, and do not ask for personal details. Prices are a snapshot from the time of each call; say so.
```

### Step 4: run it

Run the agent headless so the whole plan comes back in one reply. The `-p` flag prints the response and exits, as the [CLI reference](https://code.claude.com/docs/en/cli-reference) describes. The `--allowedTools` flag lists tools that run without a permission prompt, and per the [permissions docs](https://code.claude.com/docs/en/permissions) a bare server name such as `mcp__perceptlayer` matches every tool that server provides.

```bash
claude -p "$(cat trip-prompt.txt)" --allowedTools "mcp__perceptlayer"
```

Watch the credits used and remaining that `call_endpoint` reports after each call. If the agent exceeds six paid calls, tighten the prompt. For hard spending limits around an agent, see [AI agent API cost control](https://perceptlayer.com/blog/ai-agent-web-data-costs).

## What do the tool calls return?

The flight search returns a `flights` list. Each entry has a `price`, `currency`, `airline_name`, `stops`, `total_duration_minutes`, a `booking_token` and a `legs` array with the flight number and times. This is an excerpt of a recorded response for `GET /v1/google-flights/one-way` with `origin=JFK`, `destination=LAX` and `departure_date=2026-12-15`.

```json
{
  "flights": [
    {
      "price": 179,
      "currency": "USD",
      "total_duration_minutes": 384,
      "stops": 0,
      "airline_name": "JetBlue",
      …
      "legs": [
        {
          "airline": "B6",
          "flight_number": "2223",
          "departure_airport": "JFK",
          "arrival_airport": "LAX",
          "departure_time": "2026-12-15T08:01:00",
          "arrival_time": "2026-12-15T11:25:00",
          "duration_minutes": 384
        }
      ]
    },
    …
  ],
  …
}
```

The hotel search returns a `properties` list. This excerpt is from a separate recorded request, `GET /v1/google-hotels/search` for `Paris, France` with `check_in_date=2026-12-15` and `check_out_date=2026-12-18`. It shows the shape of the data, not the Los Angeles results.

```json
{
  "properties": [
    {
      "type": "hotel",
      "name": "Novotel Paris Porte de Versailles",
      …
      "property_token": "ChkI5rWisq2xuMNRGg0vZy8xMWhfa18xc2w4EAE",
      …
      "rate_per_night": {
        "lowest": "€136",
        "extracted_lowest": 136,
        …
      },
      "total_rate": {
        "lowest": "€408",
        "extracted_lowest": 408,
        …
      },
      …
      "overall_rating": 4.6,
      "reviews": 4337,
      …
      "hotel_class": "4-star hotel",
      "extracted_hotel_class": 4
    },
    …
  ],
  …
}
```

The [Booking.com API](https://perceptlayer.com/apis/booking) search takes `ss` (the place), plus `checkin`, `checkout`, `group_adults`, `dest_id` and `dest_type`. It returns property results for those dates. We have no recorded response for it, so check its fields in the playground on the API page rather than assuming them.

## How can you run the same plan without an LLM?

You can run the flight and hotel legs as plain HTTP calls. This is useful for testing, for checking what the agent should have found, and for a scheduled job that doesn't need reasoning. The script below makes three paid calls (3 credits): two one-way flight searches and one hotel search. It reads only fields shown in the recorded responses above.

### Step 5: run the script

Save this as `plan_trip.py` and install `requests` first. The script retries 429 and 503 responses after the `Retry-After` delay, and stops with the error code on any other failure.

```python
import os
import sys
import time
from datetime import date

import requests

BASE = "https://api.perceptlayer.com"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}
spent = 0
remaining = None

def call(path, **params):
    global spent, remaining
    for _ in range(3):
        r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=60)
        if r.status_code in (429, 503):
            time.sleep(int(r.headers.get("Retry-After", 5)))
            continue
        break
    if not r.ok:
        err = r.json()["error"]
        sys.exit(f"{err['code']} ({r.status_code}): {err['message']} [{err['request_id']}]")
    spent += int(r.headers["X-Credits-Used"])
    remaining = r.headers["X-Credits-Remaining"]
    return r.json()

def cheapest_flight(origin, destination, day):
    data = call(
        "/v1/google-flights/one-way",
        origin=origin,
        destination=destination,
        departure_date=day,
        adults=1,
        sort_by="cheapest",
    )
    flights = data["flights"]
    if not flights:
        sys.exit(f"No flights {origin}-{destination} on {day}")
    best = min(flights, key=lambda f: f["price"])
    leg = best["legs"][0]
    print(
        f"{origin}->{destination} {day}: {best['airline_name']} "
        f"{leg['airline']}{leg['flight_number']} dep {leg['departure_time']} "
        f"{best['stops']} stops, {best['price']} {best['currency']}"
    )

def top_hotels(query, check_in, check_out):
    data = call(
        "/v1/google-hotels/search",
        q=query,
        check_in_date=check_in,
        check_out_date=check_out,
        adults=1,
        currency="USD",
        gl="us",
    )
    props = [
        p for p in data["properties"]
        if p.get("overall_rating") and p.get("total_rate")
    ]
    props = [p for p in props if p["overall_rating"] >= 4.0]
    props.sort(key=lambda p: p["total_rate"]["extracted_lowest"])
    for p in props[:3]:
        print(
            f"{p['name']}: {p['overall_rating']} stars from {p['reviews']} reviews, "
            f"{p['total_rate']['lowest']} total"
        )

origin, destination, hotel_query, check_in, check_out = sys.argv[1:6]
nights = (date.fromisoformat(check_out) - date.fromisoformat(check_in)).days
print(f"{nights} nights")
cheapest_flight(origin, destination, check_in)
cheapest_flight(destination, origin, check_out)
top_hotels(hotel_query, check_in, check_out)
print(f"Credits used: {spent}, remaining: {remaining}")
```

```bash
pip install requests
python plan_trip.py JFK LAX "Los Angeles, California" 2026-12-15 2026-12-18
```

The output is a list of the cheapest outbound and return fares, the three lowest-priced hotels rated 4.0 or higher, and a credits line. Three calls cost 3 credits. If a request times out on your side, don't assume it failed: requests have no idempotency key, so it may have succeeded and been billed. [Retrying web API calls without paying twice](https://perceptlayer.com/blog/retry-web-api-calls-without-paying-twice) covers that case.

## What does one trip plan cost?

One trip plan costs 6 credits, because six `call_endpoint` calls each cost 1 credit and the catalogue lookups are free. Failed calls cost nothing and return `X-Credits-Used: 0`. From the [credits docs](https://docs.perceptlayer.com/credits), a successful call costs the credits shown on its endpoint, so the plan's cost in credits is the same on every pack.

| Credit source | Credits | Trip plans (6 credits each) |
| --- | --- | --- |
| Free tier, per month | 1,000 | 166 |
| Starter pack | 10,000 | 1,666 |
| Build pack | 50,000 | 8,333 |
| Growth pack | 200,000 | 33,333 |
| Pro pack | 500,000 | 83,333 |
| Scale pack | 1,000,000 | 166,666 |
| Volume pack | 5,000,000 | 833,333 |

The arithmetic: 10,000 credits ÷ 6 = 1,666 plans on the Starter pack, and 500,000 credits ÷ 6 = 83,333 plans on the Pro pack. One plan is 6 calls × 1 credit = 6 credits, and the free tier's 1,000 credits ÷ 6 = 166 plans. Plans are rounded down to whole trips. Purchased credits last 12 months, and there is no subscription. For what a pack of credits costs, see [pricing](https://perceptlayer.com/pricing).

Each pack includes a request rate, and a faster rate can be chosen at checkout. See [pricing](https://perceptlayer.com/pricing) for the packs and the rate options, and the [rate limits docs](https://docs.perceptlayer.com/rate-limits) for how limits are counted. An agent that fires several searches at once can hit the limit on a free account, which gets 1 request a second.

If the agent re-plans a trip with a different date, each new search is another paid call. Caching identical searches avoids paying twice; [caching API responses in Python](https://perceptlayer.com/blog/cache-web-data-api-responses) shows how.

## Why does the agent read prices but never book?

The agent reads public prices and stops there. Booking means taking payment, holding personal details and acting for the traveller, and a language model that misreads one parameter can spend someone's money. Keeping the agent read-only means the worst outcome is a wrong summary, which a person can check before acting.

There is a second reason. Data is collected from publicly available sources and returned as found at the time of the call. A fare or room rate can change between your call and the traveller's click, so the agent should present results as a snapshot and send the traveller to the airline or hotel to confirm the price. That belongs in the prompt, as step 3 shows.

The Google Flights API does include a [Booking Details](https://perceptlayer.com/apis/google-flights/booking-details) endpoint, which takes a `booking_token` and the flight's details. This tutorial leaves it out on purpose: it adds a paid call per fare and moves the agent a step closer to checkout. Add it only if your product shows fare details and a person makes the decision.

## What can go wrong, and how does the agent recover?

Most failures are parameter mistakes or rate limits, and each one has a code the agent can act on. Errors return an HTTP status and a JSON body with `error.code`, `error.message` and `error.request_id`. Because failed calls aren't billed, a wrong guess costs time, not credits.

| Code | Status | What it means for the agent |
| --- | --- | --- |
| `invalid_parameters` | 422 | A parameter is missing or malformed. Re-read the endpoint with `get_endpoint` and fix the call. |
| `rate_limited` | 429 | Wait for the `Retry-After` seconds, then retry. |
| `temporarily_unavailable` | 503 | Wait for the `Retry-After` seconds, then retry. |
| `source_error` | 502 | The source failed. Try again later or report that this search is unavailable. |
| `insufficient_credits` | 402 | The balance is empty. Stop and tell the user. |

The full list is in the [errors docs](https://docs.perceptlayer.com/errors), and the limits are in the [rate limits docs](https://docs.perceptlayer.com/rate-limits). The next step is to run the prompt: [add the MCP server](https://docs.perceptlayer.com/ai-agents) and plan your first trip with the free credits.

## APIs in this post

-   [Google Flights **1 credit per call** · 7 endpoints](https://perceptlayer.com/apis/google-flights)
-   [Booking.com **1 credit per call** · 11 endpoints](https://perceptlayer.com/apis/booking)
-   [Google Hotels **1 credit per call** · 3 endpoints](https://perceptlayer.com/apis/google-hotels)

## Frequently asked questions

### Does the agent book flights or hotels?

No. It reads public prices through search endpoints and returns a summary. Nothing in the tools reserves or pays for anything, and the prompt tells the agent not to try. A person confirms the price on the airline or hotel site before booking.

### How many credits does one trip plan use?

Six in this tutorial: one date-range search, two one-way flight searches, two hotel searches and one reviews lookup, each 1 credit. The catalogue lookups, `search_endpoints` and `get_endpoint`, cost nothing, and failed calls aren't billed.

### Can I use an MCP client other than Claude Code?

Yes. The server is at `https://api.perceptlayer.com/mcp` and uses Streamable HTTP, so any client that supports remote MCP servers can connect. Authenticate with your key as a bearer token, as in the Claude Code command in step 2.

### What happens when I run out of credits mid-plan?

The next call returns `insufficient_credits` (402) and nothing more is debited. The service is prepaid with no automatic top-up, so the agent can't overspend your balance. The prompt should tell it to stop and report what it already found.

### Are the prices live?

They are returned as found at the time of each call, from publicly available sources. A fare or room rate can change afterwards, so present the results as a snapshot with the time of the call, and have the traveller confirm before booking.

travel mcp ai agents flights hotels

## More from the blog

1.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 16 Oct 2026 Perceptlayer 8 min read
    
    ## [MCP Server Authentication: API Keys vs OAuth](https://perceptlayer.com/blog/mcp-server-api-key-vs-oauth)
    
    The MCP spec makes OAuth 2.1 optional for remote servers. When a static API key in an Authorization header fits better, and how clients send one.
    
2.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 12 Oct 2026 Perceptlayer 7 min read
    
    ## [MCP Server Too Many Tools: Keeping Agent Context Small](https://perceptlayer.com/blog/mcp-too-many-tools-context)
    
    One tool per endpoint for 1,659 endpoints is about 567,000 characters of schema. A 10-step checklist to keep an MCP server's tool list small.
    
3.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 2 Oct 2026 Perceptlayer 7 min read
    
    ## [How to Add an MCP Server to Claude Desktop and Cursor](https://perceptlayer.com/blog/add-mcp-web-data-tools-claude-desktop)
    
    Add the Perceptlayer MCP server to Claude Desktop or Cursor with an API key header, run a first Google search, and check only successful calls cost credits.
