709 lines
22 KiB
Markdown
709 lines
22 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
|
||
|
||
---
|
||
|
||
## Remote Access Entities (Added 2026-04-07)
|
||
|
||
The following entities support remote MCP access over HTTP/2 with rate limiting, health monitoring, and global PNR retrieval.
|
||
|
||
### 8. Remote Connection
|
||
|
||
Represents an active remote client connection over HTTP/2.
|
||
|
||
**Fields**:
|
||
```typescript
|
||
{
|
||
connectionId: string; // Unique connection identifier (UUID)
|
||
sessionId: string; // Associated MCP session ID
|
||
remoteIP: string; // Client IP address (for rate limiting)
|
||
connectedAt: number; // Unix timestamp (ms)
|
||
lastActivityAt: number; // Unix timestamp (ms)
|
||
transportType: string; // 'http' | 'stdio'
|
||
userAgent: string; // Client user agent
|
||
requestCount: number; // Total requests made in this connection
|
||
}
|
||
```
|
||
|
||
**Storage**: Valkey hash at key `connection:{connectionId}`
|
||
|
||
**Validation**:
|
||
- `remoteIP` must be valid IPv4 or IPv6 format
|
||
- `transportType` must be 'http' or 'stdio'
|
||
- `connectedAt` must be <= `lastActivityAt`
|
||
|
||
**State Transitions**:
|
||
```
|
||
[Connected] → [Active] → [Idle] → [Disconnected]
|
||
↓
|
||
[Expired]
|
||
```
|
||
|
||
**Lifecycle**:
|
||
- Created on initial HTTP request with new session
|
||
- Updated on every request (lastActivityAt, requestCount)
|
||
- Auto-expires after SESSION_TTL_HOURS of inactivity
|
||
- Explicitly deleted on graceful disconnect
|
||
|
||
---
|
||
|
||
### 9. Rate Limit Record
|
||
|
||
Tracks request rates per client IP address for abuse prevention.
|
||
|
||
**Fields**:
|
||
```typescript
|
||
{
|
||
clientIP: string; // Client IP address (key component)
|
||
currentWindow: number; // Current time window (Unix timestamp / window_seconds)
|
||
currentCount: number; // Request count in current window
|
||
previousCount: number; // Request count in previous window
|
||
limit: number; // Max requests allowed per window
|
||
resetAt: number; // Next window reset timestamp
|
||
}
|
||
```
|
||
|
||
**Storage**:
|
||
- Current window: Valkey integer at key `ratelimit:{ip}:{currentWindow}`
|
||
- Previous window: Valkey integer at key `ratelimit:{ip}:{previousWindow}`
|
||
|
||
**Validation**:
|
||
- `clientIP` must be valid IP address
|
||
- `currentCount`, `previousCount` must be non-negative integers
|
||
- `limit` must be positive integer (default: 100)
|
||
- `resetAt` must be in the future
|
||
|
||
**Algorithm**: Sliding Window Counter (Hybrid)
|
||
```javascript
|
||
estimatedCount = (previousCount * previousWeight) + currentCount
|
||
where previousWeight = 1 - (elapsedInWindow / windowSeconds)
|
||
```
|
||
|
||
**Lifecycle**:
|
||
- Counter incremented on each request: `INCR ratelimit:{ip}:{window}`
|
||
- TTL set to 2× window duration (keeps previous window accessible)
|
||
- Auto-expires after TTL (no manual cleanup needed)
|
||
- Resets at window boundary (new window key)
|
||
|
||
**Error Response** (when limit exceeded):
|
||
```typescript
|
||
{
|
||
error: "Rate limit exceeded",
|
||
code: "RATE_LIMIT_EXCEEDED",
|
||
limit: 100,
|
||
current: 105,
|
||
resetAt: "2026-04-07T10:01:00.000Z",
|
||
retryAfter: 15 // seconds
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 10. Health Status
|
||
|
||
Represents server operational status for monitoring and health checks.
|
||
|
||
**Fields**:
|
||
```typescript
|
||
{
|
||
status: string; // 'healthy' | 'degraded' | 'unhealthy'
|
||
uptime: number; // Seconds since server start
|
||
version: string; // Server version
|
||
connections: {
|
||
stdio: number; // Active stdio connections (0 or 1 typically)
|
||
http: number; // Active HTTP connections
|
||
total: number; // Total active connections
|
||
},
|
||
sessions: {
|
||
active: number; // Active sessions (sessions with recent activity)
|
||
total: number; // Total sessions in Valkey
|
||
},
|
||
storage: {
|
||
connected: boolean; // Valkey connection status
|
||
responseTime: number; // Valkey ping response time (ms)
|
||
},
|
||
memory: {
|
||
used: number; // Process memory used (MB)
|
||
total: number; // Total system memory (MB)
|
||
percentage: number; // Memory usage percentage
|
||
},
|
||
timestamp: number; // Health check timestamp (Unix ms)
|
||
}
|
||
```
|
||
|
||
**Endpoint**: `GET /health` (unauthenticated, exempt from rate limiting)
|
||
|
||
**Status Determination**:
|
||
- **healthy**: All systems operational, storage connected, memory < 80%
|
||
- **degraded**: Storage slow (ping > 100ms) or memory 80-90%
|
||
- **unhealthy**: Storage disconnected or memory > 90%
|
||
|
||
**Response Codes**:
|
||
- 200 OK: status = 'healthy'
|
||
- 200 OK: status = 'degraded' (still serving requests)
|
||
- 503 Service Unavailable: status = 'unhealthy'
|
||
|
||
**Example Response**:
|
||
```json
|
||
{
|
||
"status": "healthy",
|
||
"uptime": 3600,
|
||
"version": "0.1.0",
|
||
"connections": {
|
||
"stdio": 0,
|
||
"http": 12,
|
||
"total": 12
|
||
},
|
||
"sessions": {
|
||
"active": 8,
|
||
"total": 15
|
||
},
|
||
"storage": {
|
||
"connected": true,
|
||
"responseTime": 2
|
||
},
|
||
"memory": {
|
||
"used": 45,
|
||
"total": 8192,
|
||
"percentage": 0.55
|
||
},
|
||
"timestamp": 1712486460000
|
||
}
|
||
```
|
||
|
||
**Use Cases**:
|
||
- Load balancer health checks
|
||
- Monitoring system integration (Prometheus, Datadog)
|
||
- Deployment validation (verify server started successfully)
|
||
- Debugging (check connection counts, memory usage)
|
||
|
||
---
|
||
|
||
## Updated Storage Schema (Remote Mode)
|
||
|
||
### Global PNR Storage (Session-Independent)
|
||
|
||
**Key Change**: PNRs now stored globally with TTL, not scoped to sessions.
|
||
|
||
```
|
||
pnr:{pnr} # Global PNR storage
|
||
→ {
|
||
pnr: string,
|
||
status: 'confirmed' | 'cancelled',
|
||
createdAt: ISO8601,
|
||
expiresAt: ISO8601,
|
||
creatingSessionId: string, # For logging only, not access control
|
||
segments: [...],
|
||
passengers: [...],
|
||
totalPrice: number
|
||
}
|
||
```
|
||
|
||
**TTL**: Configurable via `PNR_TTL_HOURS` (default 1 hour)
|
||
|
||
**Access**: Any session can retrieve any PNR (global retrieval)
|
||
|
||
**Expiration**: PNR auto-deleted by Valkey after TTL expires
|
||
|
||
---
|
||
|
||
### Session PNR Reference
|
||
|
||
Sessions track which PNRs they created (for `listBookings` tool):
|
||
|
||
```
|
||
session:{sessionId}:pnrs # Set of PNR codes created in this session
|
||
→ Set<string> # e.g., ["TEST-ABC123", "TEST-DEF456"]
|
||
```
|
||
|
||
**Purpose**: Enable `listBookings` to return session-created PNRs
|
||
|
||
**Lifecycle**: Deleted when session expires (PNRs persist independently)
|
||
|
||
---
|
||
|
||
### Rate Limit Keys
|
||
|
||
```
|
||
ratelimit:{ip}:{window} # Request count for IP in time window
|
||
→ integer # e.g., 45 (requests made)
|
||
TTL: windowSeconds * 2 # Keep previous window accessible
|
||
```
|
||
|
||
**Example**:
|
||
```
|
||
ratelimit:192.168.1.1:287456 # Current window (e.g., minute 287456)
|
||
→ 45
|
||
ratelimit:192.168.1.1:287455 # Previous window
|
||
→ 92
|
||
```
|
||
|
||
---
|
||
|
||
### Connection Tracking
|
||
|
||
```
|
||
connection:{connectionId} # Remote connection metadata
|
||
→ { connectionId, sessionId, remoteIP, connectedAt, ... }
|
||
TTL: SESSION_TTL_HOURS
|
||
```
|
||
|
||
**Purpose**: Track active HTTP connections for health monitoring
|
||
|
||
**Cleanup**: Auto-expires with session TTL
|
||
|
||
---
|
||
|
||
## Updated Validation Rules (Remote Mode)
|
||
|
||
### Additional Validations
|
||
|
||
1. **IP Address Validation**:
|
||
- Must be valid IPv4 (e.g., `192.168.1.1`) or IPv6 format
|
||
- Used for rate limiting and logging
|
||
- Extracted from `X-Forwarded-For` or `X-Real-IP` headers (trusted proxy)
|
||
|
||
2. **Session ID Format** (HTTP mode):
|
||
- Must be valid UUID v4
|
||
- Sent via `MCP-Session-ID` header
|
||
- Generated by transport if not provided
|
||
|
||
3. **Rate Limit Headers** (HTTP mode):
|
||
- `X-RateLimit-Limit`: Integer > 0
|
||
- `X-RateLimit-Remaining`: Integer >= 0
|
||
- `X-RateLimit-Reset`: Unix timestamp
|
||
- `Retry-After`: Seconds (when limit exceeded)
|
||
|
||
4. **CORS Headers** (HTTP mode):
|
||
- `Origin`: Any (wildcard policy)
|
||
- `Access-Control-Allow-Origin`: Must be `*`
|
||
- Preflight requests must use OPTIONS method
|
||
|
||
---
|
||
|
||
## Performance Considerations (Remote Mode)
|
||
|
||
### Additional Overhead
|
||
|
||
- **Rate Limiting**: +3 Valkey ops per request (~1-2ms overhead)
|
||
- **Connection Tracking**: +1 Valkey write per request (~0.5ms overhead)
|
||
- **CORS Preflight**: OPTIONS requests handled immediately (no tool execution)
|
||
- **Health Checks**: Separate fast path (no session/rate limit checks)
|
||
|
||
### Expected Performance (Remote)
|
||
|
||
- **Search Operations**: <2s (requirement: SC-003)
|
||
- **Booking Operations**: <500ms (includes rate limit check)
|
||
- **Retrieval Operations**: <200ms (global PNR lookup)
|
||
- **Health Check**: <100ms (Valkey ping only)
|
||
- **Concurrent Remote Sessions**: 50+ (requirement: SC-012)
|
||
|
||
### Optimization Strategies
|
||
|
||
1. **Rate Limit Caching**: Cache IP counters in-memory for 1 second (reduce Valkey ops)
|
||
2. **Connection Pooling**: Reuse HTTP/2 connections (handled by Nginx)
|
||
3. **Health Check Caching**: Cache health status for 5 seconds
|
||
4. **CORS Preflight Caching**: 24-hour `Access-Control-Max-Age`
|
||
|
||
---
|
||
|
||
## Next Steps
|
||
|
||
Data model complete with remote access entities. Proceed to:
|
||
1. ✅ Update contracts/ with health endpoint schema
|
||
2. ✅ Update quickstart.md with remote access setup
|
||
3. ✅ Run agent context update script
|
||
|