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>
395 lines
13 KiB
Markdown
395 lines
13 KiB
Markdown
# 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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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
|
|
- `originCode` ≠ `destinationCode`
|
|
- `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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
```typescript
|
|
{
|
|
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
|