Files
gds-mock-mcp/specs/001-mock-gds-server/data-model.md
Peter.Morton 35a98c6d4b feat: complete implementation plan for Mock GDS MCP server
Phase 0 (Research):
- Selected MCP SDK (@modelcontextprotocol/sdk) for protocol compliance
- Chose ioredis for Valkey client (Redis-compatible)
- Minimal dependencies: Pino logging, native test runner
- Docker buildx bake for multi-platform builds
- Embedded mock data with deterministic generation
- PNR format: TEST-{BASE32} for safety

Phase 1 (Design):
- Data model: 8 core entities (Session, PNR, FlightSegment, etc.)
- MCP contracts: 8 tools with JSON schemas
- Quickstart guide with complete workflow examples
- Constitution compliance verified

Technical stack:
- Node.js 20 LTS
- Valkey 8.0+ for persistence
- Docker containers (amd64/arm64)
- Performance: <2s search, 50+ concurrent sessions

Ready for task generation with /speckit.tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-07 11:27:53 -05:00

13 KiB

Data Model: Mock GDS MCP Server

Branch: 001-mock-gds-server | Date: 2026-04-07

Overview

This document defines the core data entities, their relationships, validation rules, and state transitions for the Mock GDS MCP server.

Core Entities

1. Session

Represents an MCP client connection with isolated booking context.

Fields:

{
  id: string;              // Unique session identifier (UUID)
  createdAt: number;       // Unix timestamp (ms)
  expiresAt: number;       // Unix timestamp (ms), TTL-based
  lastActivity: number;    // Unix timestamp (ms)
  bookingCount: number;    // Number of bookings created in session
  searchCount: number;     // Number of searches performed
}

Storage: Valkey hash at key gds:session:{sessionId}

Validation:

  • id must be valid UUID v4
  • expiresAt must be > createdAt
  • Automatic expiry via Valkey TTL (default 1 hour)

State Transitions:

[Created] → [Active] → [Expired]
          ↓
       [Ended]

2. Passenger

Represents a traveler in a booking.

Fields:

{
  id: string;              // Unique within booking
  type: enum;              // 'adult' | 'child' | 'infant'
  firstName: string;       // Required
  lastName: string;        // Required
  dateOfBirth: string;     // ISO 8601 date (YYYY-MM-DD), optional
  email: string;           // Email address, optional
  phone: string;           // Phone number, optional
  frequentFlyerNumber: string;  // Airline loyalty number, optional
}

Validation:

  • firstName, lastName must be 1-50 characters, alphabetic + spaces/hyphens
  • email must match RFC 5322 pattern if provided
  • phone must match E.164 pattern if provided
  • type must be valid enum value

3. FlightSegment

Represents a single flight leg in an itinerary.

Fields:

{
  id: string;              // Unique segment identifier
  flightNumber: string;    // e.g., "AA123"
  airlineCode: string;     // IATA 2-letter code (e.g., "AA")
  airlineName: string;     // Full airline name
  originCode: string;      // IATA 3-letter airport code (e.g., "JFK")
  originName: string;      // Airport name
  destinationCode: string; // IATA 3-letter airport code
  destinationName: string; // Airport name
  departureTime: string;   // ISO 8601 datetime
  arrivalTime: string;     // ISO 8601 datetime
  duration: number;        // Minutes
  aircraftType: string;    // e.g., "Boeing 737-800"
  cabin: enum;             // 'economy' | 'premium_economy' | 'business' | 'first'
  price: number;           // USD cents (e.g., 29900 = $299.00)
  seatsAvailable: number;  // Available seats count
  bookingClass: string;    // Fare class code (e.g., "Y", "J", "F")
  status: enum;            // 'available' | 'sold_out' | 'cancelled'
}

Validation:

  • airlineCode must be valid IATA 2-letter code
  • originCode, destinationCode must be valid IATA 3-letter codes
  • originCodedestinationCode
  • departureTime < arrivalTime
  • duration must match calculated time difference
  • seatsAvailable must be >= 0
  • price must be > 0

Business Rules:

  • Flight times must be realistic for route (e.g., JFK→LAX ~6 hours)
  • Prices scale with distance and cabin class
  • sold_out status when seatsAvailable = 0

4. HotelReservation

Represents a hotel booking segment.

Fields:

{
  id: string;              // Unique reservation identifier
  hotelCode: string;       // Internal hotel identifier
  hotelName: string;       // Hotel property name
  chainCode: string;       // Hotel chain code (e.g., "MAR" for Marriott)
  chainName: string;       // Hotel chain name
  address: string;         // Full address
  cityCode: string;        // IATA city code (e.g., "LAX")
  cityName: string;        // City name
  checkInDate: string;     // ISO 8601 date (YYYY-MM-DD)
  checkOutDate: string;    // ISO 8601 date (YYYY-MM-DD)
  nights: number;          // Calculated night count
  roomType: string;        // e.g., "Standard King", "Deluxe Suite"
  rateCode: string;        // Rate plan code
  starRating: number;      // 1-5 stars
  price: number;           // USD cents, total for stay
  pricePerNight: number;   // USD cents
  guestCount: number;      // Number of guests
  amenities: string[];     // List of amenities
  status: enum;            // 'available' | 'sold_out' | 'confirmed' | 'cancelled'
}

Validation:

  • checkInDate < checkOutDate
  • nights must equal date difference
  • starRating must be 1-5
  • guestCount must be >= 1
  • price must equal pricePerNight * nights

Business Rules:

  • Check-in date must not be in the past
  • Minimum 1 night stay
  • Prices vary by star rating and city

5. CarRental

Represents a car rental segment.

Fields:

{
  id: string;              // Unique rental identifier
  companyCode: string;     // Rental company code (e.g., "ZE" for Hertz)
  companyName: string;     // Company name
  pickupLocationCode: string;  // Airport code or location ID
  pickupLocationName: string;  // Location name
  dropoffLocationCode: string; // Airport code or location ID
  dropoffLocationName: string; // Location name
  pickupDate: string;      // ISO 8601 datetime
  dropoffDate: string;     // ISO 8601 datetime
  vehicleClass: enum;      // 'economy' | 'compact' | 'midsize' | 'fullsize' | 'suv' | 'luxury'
  vehicleModel: string;    // e.g., "Toyota Camry or similar"
  dailyRate: number;       // USD cents per day
  totalPrice: number;      // USD cents
  rentalDays: number;      // Number of days
  mileagePolicy: enum;     // 'unlimited' | 'limited'
  insuranceIncluded: boolean;
  status: enum;            // 'available' | 'confirmed' | 'cancelled'
}

Validation:

  • pickupDate < dropoffDate
  • rentalDays must match date calculation
  • totalPrice must equal dailyRate * rentalDays
  • pickupLocationCode should match airport/city code for traveler's destination

Business Rules:

  • Same-location dropoff preferred (one-way rentals add surcharge)
  • Pickup date should align with flight arrival
  • Dropoff date should align with departure flight

6. PNR (Passenger Name Record)

Represents a complete booking with multiple service segments.

Fields:

{
  pnr: string;             // Unique booking reference (format: TEST-{BASE32})
  sessionId: string;       // Session that created the booking
  createdAt: number;       // Unix timestamp (ms)
  lastModified: number;    // Unix timestamp (ms)
  status: enum;            // 'pending' | 'confirmed' | 'cancelled'
  passengers: Passenger[]; // Array of passengers
  flights: FlightSegment[]; // Array of flight segments
  hotels: HotelReservation[]; // Array of hotel bookings
  cars: CarRental[];       // Array of car rentals
  totalPrice: number;      // USD cents, sum of all segments
  currency: string;        // Always "USD" for mock data
  contactEmail: string;    // Primary contact email
  contactPhone: string;    // Primary contact phone
}

Storage: Valkey hash at key gds:session:{sessionId}:booking:{pnr}

Validation:

  • pnr must match format TEST-[A-Z0-9]{6}
  • Must have at least one passenger
  • Must have at least one service (flight, hotel, or car)
  • totalPrice must equal sum of all segment prices
  • contactEmail or contactPhone required (at least one)

State Transitions:

[Pending] → [Confirmed] → [Cancelled]
           ↓
       [Modified] → [Confirmed]

Business Rules:

  • Cannot modify after cancellation
  • Hotel dates must overlap with flight dates
  • Car pickup should align with flight arrival
  • Multi-city bookings require connecting flights

7. SearchQuery

Represents a search request (flights, hotels, or cars).

Fields:

{
  id: string;              // Unique search identifier
  sessionId: string;       // Session performing search
  type: enum;              // 'flight' | 'hotel' | 'car'
  timestamp: number;       // Unix timestamp (ms)
  parameters: object;      // Type-specific search params
  resultCount: number;     // Number of results returned
  responseTime: number;    // Milliseconds to generate results
}

Storage: Ephemeral (not persisted), tracked for statistics only

8. MockDataRecord

Represents a static mock data entry (airports, airlines, hotels, etc.).

Fields:

{
  type: enum;              // 'airport' | 'airline' | 'hotel' | 'car_company'
  code: string;            // IATA/ICAO code or internal ID
  name: string;            // Full name
  metadata: object;        // Type-specific data (coordinates, address, etc.)
}

Storage: In-memory JavaScript modules, not in Valkey

Relationships

Session ↔ PNR

  • Type: One-to-Many
  • Description: A session can create multiple bookings
  • Key: sessionId in PNR references Session
  • Cascade: PNRs remain accessible after session expires (for retrieval)

PNR ↔ Passengers

  • Type: One-to-Many
  • Description: A booking contains multiple passengers
  • Embedded: Passengers stored within PNR document
  • Constraint: Minimum 1 passenger per PNR

PNR ↔ FlightSegments

  • Type: One-to-Many
  • Description: A booking can include multiple flight legs
  • Embedded: Flights stored within PNR document
  • Ordering: Flights ordered chronologically

PNR ↔ HotelReservations

  • Type: One-to-Many
  • Description: A booking can include multiple hotel stays
  • Embedded: Hotels stored within PNR document

PNR ↔ CarRentals

  • Type: One-to-Many
  • Description: A booking can include multiple car rentals
  • Embedded: Cars stored within PNR document

Data Validation Rules

Cross-Entity Validation

  1. Date Consistency:

    • Hotel check-in must be >= flight arrival date
    • Hotel check-out must be <= return flight departure date
    • Car pickup must be >= flight arrival date
    • Car dropoff must be <= return flight departure date
  2. Location Consistency:

    • Hotel city should match flight destination
    • Car pickup location should match airport or destination city
  3. Passenger Consistency:

    • All segments in a PNR share the same passenger list
    • Passenger count must match across segments
  4. Pricing Integrity:

    • PNR total must equal sum of all segment prices
    • Segment prices must be positive integers

Validation Timing

  • Search-time: Parameter validation (dates, codes, counts)
  • Booking-time: Business rule validation (date logic, location consistency)
  • Retrieval-time: PNR format validation
  • Modification-time: State transition validation (no modification of cancelled bookings)

Mock Data Generation Rules

Deterministic Generation

  • Same search inputs → same results (when MOCK_DATA_SEED=fixed)
  • PNR generation uses session-scoped sequence + timestamp hash
  • Flight schedules fixed based on route (JFK→LAX always ~6 hours)

Realistic Constraints

  • Flight prices: $200-$800 domestic economy, $800-$2000 business, $2500+ first
  • Hotel prices: $80-$150 budget, $150-$300 midrange, $300-$800 luxury
  • Car rental: $35-$50 economy, $50-$80 midsize, $100-$150 luxury
  • Flight duration: Calculated from route distance
  • Availability: 90% flights available, 10% sold out

Data Coverage

  • Airports: ~100 major airports (top 50 US + top 50 international)
  • Airlines: ~30 major carriers
  • Hotels: ~50 chains/properties across major cities
  • Car Companies: ~6 major rental companies

State Management (Valkey)

Key Naming Convention

gds:session:{sessionId}                     # Session metadata
gds:session:{sessionId}:booking:{pnr}       # Individual booking
gds:session:{sessionId}:bookings            # Set of all PNRs in session
gds:session:{sessionId}:searches            # List of search IDs
gds:stats:bookings:total                    # Global booking counter
gds:stats:sessions:active                   # Set of active session IDs

TTL Strategy

  • Sessions: 1 hour (configurable via MCP_SESSION_TIMEOUT)
  • Bookings: No expiry (persist beyond session for retrieval)
  • Search history: 10 minutes (ephemeral)

Data Serialization

  • Format: JSON strings for complex objects
  • Encoding: UTF-8
  • Compression: None (mock data is small)

Performance Considerations

Memory Footprint

  • Per Session: ~5KB (metadata only)
  • Per Booking: ~10-30KB (depends on segment count)
  • Mock Data: ~2MB (embedded in code, not in Valkey)

Query Patterns

  • Hot Path: GET gds:session:{sessionId}:booking:{pnr} (booking retrieval)
  • Write Path: HSET gds:session:{sessionId}:booking:{pnr} (booking creation)
  • Cleanup: SCAN + DEL for expired sessions (background job)

Indexing

No secondary indexes required - all queries use primary keys (sessionId, pnr)

Next Steps

Proceed to contract definition (Phase 1 continued):

  • Define MCP tool schemas in /contracts/mcp-tools.md
  • Create quickstart guide with example usage