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>
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:
idmust be valid UUID v4expiresAtmust 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,lastNamemust be 1-50 characters, alphabetic + spaces/hyphensemailmust match RFC 5322 pattern if providedphonemust match E.164 pattern if providedtypemust 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:
airlineCodemust be valid IATA 2-letter codeoriginCode,destinationCodemust be valid IATA 3-letter codesoriginCode≠destinationCodedepartureTime<arrivalTimedurationmust match calculated time differenceseatsAvailablemust be >= 0pricemust 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<checkOutDatenightsmust equal date differencestarRatingmust be 1-5guestCountmust be >= 1pricemust equalpricePerNight*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<dropoffDaterentalDaysmust match date calculationtotalPricemust equaldailyRate*rentalDayspickupLocationCodeshould 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:
pnrmust match formatTEST-[A-Z0-9]{6}- Must have at least one passenger
- Must have at least one service (flight, hotel, or car)
totalPricemust equal sum of all segment pricescontactEmailorcontactPhonerequired (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:
sessionIdin 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
-
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
-
Location Consistency:
- Hotel city should match flight destination
- Car pickup location should match airport or destination city
-
Passenger Consistency:
- All segments in a PNR share the same passenger list
- Passenger count must match across segments
-
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+DELfor 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