Skip to main content
Glama

book_with_points

Search for Southwest Airlines flights and book using Rapid Rewards points. View points pricing across fare classes and complete bookings through your logged-in account.

Instructions

Search for flights and book using Rapid Rewards points. Shows points pricing for all fare classes. Requires logged-in account.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
originAirportYesOrigin airport code
destinationAirportYesDestination airport code
departureDateYesDeparture date YYYY-MM-DD
returnDateNoReturn date YYYY-MM-DD (for round trip)
passengersNo
tripTypeNo
usernameNoRapid Rewards account number (uses SW_USERNAME env var if not set)
passwordNoAccount password (uses SW_PASSWORD env var if not set)

Implementation Reference

  • The main handler function 'bookWithPoints' that implements the tool logic. It authenticates the user, searches for Southwest Airlines flights with points pricing, and returns available flights with points fares for different fare classes (Wanna Get Away, Wanna Get Away Plus, Anytime, Business Select).
    export async function bookWithPoints(input: BookWithPointsInput) {
      const page = await getPage();
    
      const username = input.username || process.env.SW_USERNAME;
      const password = input.password || process.env.SW_PASSWORD;
    
      if (!username || !password) {
        return {
          success: false,
          message:
            "Login required for points booking. Set SW_USERNAME and SW_PASSWORD environment variables.",
        };
      }
    
      // Log in
      const alreadyLoggedIn = await ensureLoggedIn(page);
      if (!alreadyLoggedIn) {
        await login(page, username, password);
      }
    
      // Search flights with points fare type
      const params = new URLSearchParams({
        "originationAirportCode": input.originAirport.toUpperCase(),
        "destinationAirportCode": input.destinationAirport.toUpperCase(),
        "returnAirportCode": "",
        "departureDate": input.departureDate,
        "returnDate": input.returnDate || "",
        "adultsCount": input.passengers.toString(),
        "seniorCount": "0",
        "infantsInSeat": "0",
        "lapChildCount": "0",
        "tripType": input.tripType === "roundtrip" ? "roundtrip" : "oneWay",
        "fareType": "PTS",
        "passengerType": "ADULT",
      });
    
      await page.goto(
        `https://www.southwest.com/air/booking/select.html?${params.toString()}`,
        { waitUntil: "networkidle", timeout: 30000 }
      );
    
      await Promise.race([
        page.waitForSelector('[data-qa="flight-card"]', { timeout: 20000 }),
        page.waitForSelector(".no-flights-found", { timeout: 20000 }),
      ]).catch(() => {});
    
      // Extract available flights with points pricing
      const flights = await page.evaluate(() => {
        const cards = document.querySelectorAll('[data-qa="flight-card"]');
        const results: Array<{
          flightNumber: string;
          departureTime: string;
          arrivalTime: string;
          duration: string;
          stops: string;
          pointsFares: {
            wannaGetAway: string | null;
            wannaGetAwayPlus: string | null;
            anytime: string | null;
            businessSelect: string | null;
          };
        }> = [];
    
        cards.forEach((card) => {
          results.push({
            flightNumber:
              card.querySelector('[data-qa="flight-numbers-departure"]')?.textContent?.trim() || "",
            departureTime:
              card.querySelector('[data-qa="departure-time"]')?.textContent?.trim() || "",
            arrivalTime:
              card.querySelector('[data-qa="arrival-time"]')?.textContent?.trim() || "",
            duration:
              card.querySelector('[data-qa="duration"]')?.textContent?.trim() || "",
            stops:
              card.querySelector('[data-qa="stops"]')?.textContent?.trim() || "Nonstop",
            pointsFares: {
              wannaGetAway:
                card.querySelector('[data-qa="fare-button--wga"]')?.textContent?.trim() || null,
              wannaGetAwayPlus:
                card.querySelector('[data-qa="fare-button--wgaplus"]')?.textContent?.trim() || null,
              anytime:
                card.querySelector('[data-qa="fare-button--anytime"]')?.textContent?.trim() || null,
              businessSelect:
                card.querySelector('[data-qa="fare-button--business-select"]')?.textContent?.trim() || null,
            },
          });
        });
    
        return results;
      });
    
      // Get current points balance
      const pointsBalance = await page
        .locator('[data-qa="rapid-rewards-points"], .points-balance')
        .textContent()
        .catch(() => null);
    
      return {
        success: true,
        origin: input.originAirport.toUpperCase(),
        destination: input.destinationAirport.toUpperCase(),
        departureDate: input.departureDate,
        flightCount: flights.length,
        flights,
        currentPointsBalance: pointsBalance?.trim() || "Check your account",
        message:
          flights.length > 0
            ? `Found ${flights.length} flights. Use select_flight with a fare index to book with points.`
            : "No flights found. Try different dates or airports.",
        note: "Points bookings also require taxes/fees (typically $5.60 one-way for domestic). Points bookings are fully refundable — points return to your account upon cancellation.",
      };
    }
  • Zod schema 'bookWithPointsSchema' defining input validation for the tool: originAirport (3-char code), destinationAirport (3-char code), departureDate, returnDate (optional), passengers (1-8), tripType (roundtrip/oneway), and optional username/password for authentication.
    export const bookWithPointsSchema = z.object({
      originAirport: z.string().length(3).describe("Origin airport code"),
      destinationAirport: z.string().length(3).describe("Destination airport code"),
      departureDate: z.string().describe("Departure date YYYY-MM-DD"),
      returnDate: z.string().optional().describe("Return date YYYY-MM-DD (for round trip)"),
      passengers: z.number().int().min(1).max(8).default(1),
      tripType: z.enum(["roundtrip", "oneway"]).default("oneway"),
      username: z
        .string()
        .optional()
        .describe("Rapid Rewards account number (uses SW_USERNAME env var if not set)"),
      password: z
        .string()
        .optional()
        .describe("Account password (uses SW_PASSWORD env var if not set)"),
    });
  • src/index.ts:163-168 (registration)
    Tool registration in the TOOLS array defining 'book_with_points' with its description and JSON schema derived from the Zod schema.
    {
      name: "book_with_points",
      description:
        "Search for flights and book using Rapid Rewards points. Shows points pricing for all fare classes. Requires logged-in account.",
      inputSchema: zodToJsonSchema(bookWithPointsSchema),
    },
  • src/index.ts:232-234 (registration)
    Switch case handler that invokes bookWithPoints when the tool 'book_with_points' is called, parsing args with the schema.
    case "book_with_points":
      result = await bookWithPoints(bookWithPointsSchema.parse(args));
      break;
  • Browser automation helper functions used by bookWithPoints: getPage() for getting a Playwright page instance, ensureLoggedIn() for checking/establishing authentication, and login() for authenticating with Southwest Airlines credentials.
    export async function getPage(): Promise<Page> {
      const ctx = await getContext();
      const pages = ctx.pages();
      if (pages.length > 0) {
        return pages[0];
      }
      return ctx.newPage();
    }
    
    export async function saveSession(): Promise<void> {
      if (!context) return;
      const dir = path.dirname(SESSION_FILE);
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
      const storageState = await context.storageState();
      fs.writeFileSync(SESSION_FILE, JSON.stringify(storageState, null, 2));
    }
    
    export async function closeBrowser(): Promise<void> {
      if (context) {
        await saveSession();
        await context.close();
        context = null;
      }
      if (browser) {
        await browser.close();
        browser = null;
      }
    }
    
    export async function ensureLoggedIn(page: Page): Promise<boolean> {
      // Check if we're already logged in by looking for account indicators
      await page.goto("https://www.southwest.com/", { waitUntil: "networkidle" });
    
      const loggedIn = await page
        .locator('[data-qa="header-account-button"]')
        .isVisible()
        .catch(() => false);
    
      if (!loggedIn) {
        const username = process.env.SW_USERNAME;
        const password = process.env.SW_PASSWORD;
    
        if (!username || !password) {
          return false;
        }
    
        await login(page, username, password);
        return true;
      }
    
      return true;
    }
    
    export async function login(
      page: Page,
      username: string,
      password: string
    ): Promise<void> {
      await page.goto("https://www.southwest.com/account/login", {
        waitUntil: "networkidle",
      });
    
      await page.fill('[name="userNameOrAccountNumber"]', username);
      await page.fill('[name="password"]', password);
      await page.click('[data-qa="login-btn"]');
      await page.waitForNavigation({ waitUntil: "networkidle" }).catch(() => {});
    
      await saveSession();
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions that the tool 'book[s]' (implying a write/mutation operation) and 'requires logged-in account' (hinting at authentication needs), but it lacks details on critical behaviors: whether booking is reversible, what happens on failure, rate limits, or what the output looks like. For a mutation tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: two sentences that cover the core functionality and a key requirement. Every sentence adds value—the first defines the tool's purpose, and the second states an essential prerequisite. There's no redundant or wasted text, though it could be slightly more structured for clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (8 parameters, mutation operation, no annotations, no output schema), the description is incomplete. It doesn't explain the booking process outcome, error handling, or how points are applied. For a tool that performs a significant action like flight booking, more context is needed to guide the agent effectively, especially without annotations or output schema to fill gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 75%, with most parameters well-documented in the schema (e.g., airport codes, dates, trip type enum). The description adds minimal value beyond the schema, only implying that 'username' and 'password' relate to 'logged-in account' requirements. It doesn't explain parameter interactions (e.g., how 'returnDate' works with 'tripType') or provide additional context, so the baseline 3 is appropriate given the schema does most of the work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search for flights and book using Rapid Rewards points. Shows points pricing for all fare classes.' It specifies the verb ('search and book'), resource ('flights'), and mechanism ('using Rapid Rewards points'). However, it doesn't explicitly differentiate from sibling tools like 'search_flights' or 'checkout' beyond mentioning points usage.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides some usage context: 'Requires logged-in account' indicates a prerequisite, and the mention of 'Rapid Rewards points' implies this is for loyalty program members. However, it doesn't explicitly state when to use this tool versus alternatives like 'search_flights' (which might not handle booking) or 'checkout' (which might not handle points). The guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/markswendsen-code/mcp-southwest'

If you have feedback or need assistance with the MCP directory API, please join our Discord server