# Quick Start Guide: Mock GDS MCP Server **Branch**: `001-mock-gds-server` | **Date**: 2026-04-07 ## Overview This guide demonstrates how to use the Mock GDS MCP Server for testing travel applications. All examples use mock data with the TEST- prefix for safety. --- ## Installation & Setup ### Prerequisites - Node.js 20 LTS or later - Docker & Docker Compose (for Valkey) - MCP-compatible client ### Running with Docker Compose ```bash # Start the mock GDS server and Valkey docker-compose up -d # View logs docker-compose logs -f gds-mock-mcp # Stop services docker-compose down ``` ### Configuration Create a `.env` file (optional, defaults shown): ```bash # MCP Server MCP_TRANSPORT=stdio MCP_SESSION_TIMEOUT=3600 # Valkey VALKEY_HOST=valkey VALKEY_PORT=6379 VALKEY_PASSWORD= VALKEY_DB=0 # Logging LOG_LEVEL=info LOG_PRETTY=false # Mock Data MOCK_DATA_SEED=fixed MOCK_RESPONSE_DELAY=0 ``` --- ## Basic Workflows ### 1. Search and Book a Flight **Step 1: Search for flights** ```javascript // MCP tool call { "tool": "searchFlights", "arguments": { "origin": "JFK", "destination": "LAX", "departureDate": "2026-06-15", "passengers": { "adults": 1 }, "cabin": "economy" } } // Response { "searchId": "search_abc123", "flights": [ { "id": "flight_1", "flightNumber": "AA123", "airline": { "code": "AA", "name": "American Airlines" }, "departure": { "time": "2026-06-15T08:00:00Z", "airport": "JFK" }, "arrival": { "time": "2026-06-15T11:30:00Z", "airport": "LAX" }, "price": 29900, // $299.00 "seatsAvailable": 45, "status": "available" }, // ... more flights ], "resultCount": 5 } ``` **Step 2: Book the selected flight** ```javascript // MCP tool call { "tool": "bookFlight", "arguments": { "flightIds": ["flight_1"], "passengers": [ { "type": "adult", "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com" } ], "contactEmail": "john.doe@example.com" } } // Response { "pnr": "TEST-A1B2C3", // Note: TEST- prefix for safety "status": "confirmed", "bookingDate": "2026-04-07T16:30:00Z", "passengers": [{ "firstName": "John", "lastName": "Doe" }], "flights": [{ /* flight details */ }], "totalPrice": 29900, "currency": "USD" } ``` --- ### 2. Multi-Service Booking (Flight + Hotel) **Step 1: Book a flight (as above)** Result: `pnr = "TEST-A1B2C3"` **Step 2: Search for hotels** ```javascript { "tool": "searchHotels", "arguments": { "cityCode": "LAX", "checkInDate": "2026-06-15", "checkOutDate": "2026-06-17", "guests": 1 } } // Response { "hotels": [ { "id": "hotel_1", "name": "Marriott Los Angeles Downtown", "starRating": 4, "pricePerNight": 18000, // $180.00 "totalPrice": 36000, // 2 nights "amenities": ["WiFi", "Pool", "Gym", "Parking"] } ] } ``` **Step 3: Add hotel to existing booking** ```javascript { "tool": "bookHotel", "arguments": { "hotelId": "hotel_1", "existingPnr": "TEST-A1B2C3", // Add to flight booking "guests": [ { "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com" } ] } } // Response { "pnr": "TEST-A1B2C3", // Same PNR, now includes hotel "flights": [{ /* flight */ }], "hotels": [{ /* hotel */ }], "totalPrice": 65900 // $299 flight + $360 hotel } ``` --- ### 3. Complete Travel Package (Flight + Hotel + Car) **Step 1-2: Book flight and hotel** (as above) Result: `pnr = "TEST-A1B2C3"` **Step 3: Search for rental cars** ```javascript { "tool": "searchCars", "arguments": { "pickupLocationCode": "LAX", "pickupDate": "2026-06-15T12:00:00Z", "dropoffDate": "2026-06-17T10:00:00Z" } } // Response { "cars": [ { "id": "car_1", "companyName": "Hertz", "vehicleClass": "economy", "vehicleModel": "Toyota Corolla or similar", "dailyRate": 4500, // $45/day "totalPrice": 9000, // 2 days "mileagePolicy": "unlimited" } ] } ``` **Step 4: Add car to booking** ```javascript { "tool": "bookCar", "arguments": { "carId": "car_1", "existingPnr": "TEST-A1B2C3", "driver": { "firstName": "John", "lastName": "Doe", "age": 35 } } } // Response { "pnr": "TEST-A1B2C3", "flights": [{ /* flight */ }], "hotels": [{ /* hotel */ }], "cars": [{ /* car */ }], "totalPrice": 74900 // $299 + $360 + $90 } ``` --- ### 4. Retrieve and Manage Bookings **Retrieve a booking by PNR** ```javascript { "tool": "retrieveBooking", "arguments": { "pnr": "TEST-A1B2C3" } } // Response: Full booking details ``` **List all bookings in session** ```javascript { "tool": "listBookings", "arguments": { "status": "all" } } // Response { "bookings": [ { "pnr": "TEST-A1B2C3", "status": "confirmed", "createdAt": "2026-04-07T16:30:00Z", "passengerCount": 1, "segmentCounts": { "flights": 1, "hotels": 1, "cars": 1 }, "totalPrice": 74900 } ], "totalCount": 1 } ``` **Cancel a booking** ```javascript { "tool": "cancelBooking", "arguments": { "pnr": "TEST-A1B2C3", "reason": "Travel plans changed" } } // Response { "pnr": "TEST-A1B2C3", "status": "cancelled", "cancelledAt": "2026-04-07T17:00:00Z" } ``` --- ## Testing Scenarios ### Concurrent Sessions ```javascript // Session 1 const session1 = createMCPConnection(); const booking1 = await session1.call("bookFlight", { /* ... */ }); // booking1.pnr = "TEST-A1B2C3" // Session 2 (different MCP connection) const session2 = createMCPConnection(); const booking2 = await session2.call("bookFlight", { /* ... */ }); // booking2.pnr = "TEST-X7Y8Z9" // Different PNR // Sessions are isolated - cannot retrieve booking1 from session2 await session2.call("retrieveBooking", { pnr: "TEST-A1B2C3" }); // Error: PNR not found in current session ``` ### Error Handling **Invalid airport code** ```javascript { "tool": "searchFlights", "arguments": { "origin": "ZZZ", // Invalid code "destination": "LAX", "departureDate": "2026-06-15" } } // Error response { "code": -32602, "message": "Invalid airport code 'ZZZ': must be a valid 3-letter IATA code", "data": { "field": "origin", "value": "ZZZ" } } ``` **Date in the past** ```javascript { "tool": "searchFlights", "arguments": { "origin": "JFK", "destination": "LAX", "departureDate": "2020-01-01" // Past date } } // Error response { "code": -32002, "message": "Departure date cannot be in the past", "data": { "field": "departureDate", "value": "2020-01-01" } } ``` **Cancelled booking modification** ```javascript // First cancel await call("cancelBooking", { pnr: "TEST-A1B2C3" }); // Try to add hotel to cancelled booking await call("bookHotel", { hotelId: "hotel_1", existingPnr: "TEST-A1B2C3" }); // Error response { "code": -32002, "message": "Cannot modify cancelled booking TEST-A1B2C3", "data": { "pnr": "TEST-A1B2C3", "status": "cancelled" } } ``` --- ## Integration Testing Example ```javascript // Example integration test (using Node.js test runner) import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { MCPClient } from './mcp-client.js'; test('complete booking workflow', async (t) => { const client = new MCPClient(); // Search flights const searchResults = await client.call('searchFlights', { origin: 'JFK', destination: 'LAX', departureDate: '2026-06-15', passengers: { adults: 1 } }); assert.ok(searchResults.flights.length > 0); const flight = searchResults.flights[0]; // Book flight const booking = await client.call('bookFlight', { flightIds: [flight.id], passengers: [{ type: 'adult', firstName: 'Test', lastName: 'User', email: 'test@example.com' }], contactEmail: 'test@example.com' }); assert.match(booking.pnr, /^TEST-[A-Z0-9]{6}$/); assert.equal(booking.status, 'confirmed'); // Retrieve booking const retrieved = await client.call('retrieveBooking', { pnr: booking.pnr }); assert.equal(retrieved.pnr, booking.pnr); assert.equal(retrieved.flights.length, 1); // Cancel booking const cancelled = await client.call('cancelBooking', { pnr: booking.pnr }); assert.equal(cancelled.status, 'cancelled'); }); ``` --- ## Mock Data Reference ### Supported Airports Major US: JFK, LAX, ORD, DFW, ATL, MIA, SFO, SEA, BOS, LAS International: LHR, CDG, FRA, NRT, HKG, SYD, DXB ### Supported Airlines - American Airlines (AA) - Delta Air Lines (DL) - United Airlines (UA) - Southwest Airlines (WN) - British Airways (BA) - Virgin Atlantic (VS) - Lufthansa (LH) - Air France (AF) ### Price Ranges - **Flights**: $200-$800 (economy domestic), $800-$2000 (business), $2500+ (first) - **Hotels**: $80-$150 (budget), $150-$300 (mid-range), $300-$800 (luxury) - **Cars**: $35-$50 (economy), $50-$80 (midsize), $100-$150 (luxury) --- ## Troubleshooting ### Server won't start Check Valkey connection: ```bash docker-compose logs valkey ``` ### Session expires Default timeout: 1 hour. Adjust with `MCP_SESSION_TIMEOUT` env var. ### PNR not found Verify you're retrieving from the same session that created the booking. --- ## Next Steps - Explore all available tools in `/contracts/mcp-tools.md` - Review data model in `/data-model.md` - Check technical research in `/research.md` - See implementation tasks in `/tasks.md` (after running `/speckit.tasks`) --- ## Remote Access Setup (HTTP/2 Mode) The mock GDS server can be accessed remotely over HTTP/2, enabling distributed teams and web-based MCP clients. ### Remote Mode Configuration **Update `.env` for remote access**: ```bash # Transport Mode TRANSPORT_MODE=http # Enable HTTP transport (or 'both' for stdio + http) # HTTP Server HTTP_PORT=3000 # Internal HTTP port (proxied by nginx) HTTP_HOST=127.0.0.1 # Bind to localhost (nginx only) # Rate Limiting RATE_LIMIT_ENABLED=true RATE_LIMIT_PER_MINUTE=100 # 100 requests per minute per IP RATE_LIMIT_WINDOW_SECONDS=60 # PNR/Session Timeouts PNR_TTL_HOURS=1 # Global PNR retrieval window SESSION_TTL_HOURS=24 # Session expiration # CORS CORS_ENABLED=true CORS_ORIGINS=* # Wildcard for development CORS_MAX_AGE=86400 ``` ### Docker Compose Setup (Remote Mode) **docker-compose.yml** (with nginx reverse proxy): ```yaml version: '3.8' services: valkey: image: valkey/valkey:8.0-alpine ports: - "6379:6379" volumes: - valkey-data:/data command: valkey-server --appendonly yes mcp-server: build: . environment: TRANSPORT_MODE: http HTTP_PORT: 3000 HTTP_HOST: 0.0.0.0 VALKEY_HOST: valkey VALKEY_PORT: 6379 RATE_LIMIT_ENABLED: "true" RATE_LIMIT_PER_MINUTE: 100 PNR_TTL_HOURS: 1 LOG_LEVEL: info depends_on: - valkey ports: - "3000:3000" # For development without nginx nginx: image: nginx:alpine ports: - "8080:8080" # External HTTP/2 port volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/certs:/etc/nginx/certs:ro depends_on: - mcp-server volumes: valkey-data: ``` ### Nginx Configuration **nginx/nginx.conf**: ```nginx events { worker_connections 1024; } http { upstream mcp_backend { server mcp-server:3000; } server { listen 8080 ssl http2; server_name localhost; # TLS Configuration ssl_certificate /etc/nginx/certs/cert.pem; ssl_certificate_key /etc/nginx/certs/key.pem; ssl_protocols TLSv1.2 TLSv1.3; # MCP Endpoints location /mcp { proxy_pass http://mcp_backend; proxy_http_version 1.1; # Headers for rate limiting and logging proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; # SSE Support (for MCP streaming) proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; proxy_read_timeout 3600s; # CORS (optional - can be handled by app) add_header Access-Control-Allow-Origin * always; add_header Access-Control-Expose-Headers 'X-RateLimit-Limit, X-RateLimit-Remaining' always; } # Health Check location /health { proxy_pass http://mcp_backend/health; proxy_http_version 1.1; access_log off; # Don't log health checks } } } ``` ### Generate Self-Signed Certificate (Development) ```bash # Create certs directory mkdir -p nginx/certs # Generate self-signed certificate (valid 365 days) openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout nginx/certs/key.pem \ -out nginx/certs/cert.pem \ -subj "/C=US/ST=Dev/L=Local/O=MockGDS/CN=localhost" # Set permissions chmod 644 nginx/certs/cert.pem chmod 600 nginx/certs/key.pem ``` ### Start Remote Server ```bash # Start all services docker-compose up -d # Check status docker-compose ps # View logs docker-compose logs -f mcp-server nginx # Test health endpoint curl -k https://localhost:8080/health # Test CORS curl -i -H "Origin: https://example.com" -X OPTIONS https://localhost:8080/mcp ``` --- ## Connecting Remote MCP Clients ### Web-Based Client (Browser) ```javascript // Example: Connect from browser-based MCP client const mcpClient = new MCPClient({ url: 'https://localhost:8080/mcp', transport: 'sse', // Server-sent events sessionId: generateSessionId() // Or let server generate }); await mcpClient.connect(); // Call tools const flights = await mcpClient.callTool('searchFlights', { origin: 'JFK', destination: 'LAX', departureDate: '2026-06-15', passengers: { adults: 1 }, cabin: 'economy' }); console.log(`Found ${flights.resultCount} flights`); ``` ### cURL Examples **Search flights**: ```bash curl -k -X POST https://localhost:8080/mcp \ -H "Content-Type: application/json" \ -H "MCP-Session-ID: $(uuidgen)" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "searchFlights", "arguments": { "origin": "JFK", "destination": "LAX", "departureDate": "2026-06-15", "passengers": { "adults": 1 }, "cabin": "economy" } }, "id": 1 }' ``` **Check health**: ```bash curl -k https://localhost:8080/health | jq ``` **Check rate limits**: ```bash # Make multiple requests and check headers for i in {1..5}; do curl -k -i https://localhost:8080/health 2>&1 | grep -i ratelimit done ``` --- ## Rate Limiting Examples ### Normal Usage ```bash # First request curl -i https://localhost:8080/health # Returns: # X-RateLimit-Limit: 100 # X-RateLimit-Remaining: 99 # X-RateLimit-Reset: 1712486520 ``` ### Rate Limit Exceeded ```bash # Make 101 requests in under 60 seconds for i in {1..101}; do curl -s https://localhost:8080/health > /dev/null done # 101st request returns: # HTTP/1.1 429 Too Many Requests # X-RateLimit-Limit: 100 # X-RateLimit-Remaining: 0 # X-RateLimit-Reset: 1712486520 # Retry-After: 15 # # { # "error": "Rate limit exceeded", # "code": "RATE_LIMIT_EXCEEDED", # "limit": 100, # "current": 101, # "resetAt": "2026-04-07T10:02:00.000Z", # "retryAfter": 15 # } ``` ### Waiting for Reset ```bash # Wait until reset time sleep 15 # Try again (should work) curl https://localhost:8080/health # Returns 200 OK with fresh rate limit ``` --- ## Global PNR Retrieval Demo Remote mode enables cross-session PNR retrieval for testing flexibility. ### Session 1: Create Booking ```bash SESSION1=$(uuidgen) curl -k -X POST https://localhost:8080/mcp \ -H "MCP-Session-ID: $SESSION1" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "bookFlight", "arguments": { "flightId": "flight_1", "passengers": [{ "type": "adult", "firstName": "John", "lastName": "Doe", "email": "john@example.com" }] } }, "id": 1 }' # Returns: { "pnr": "TEST-ABC123", ... } ``` ### Session 2: Retrieve Same PNR ```bash SESSION2=$(uuidgen) curl -k -X POST https://localhost:8080/mcp \ -H "MCP-Session-ID: $SESSION2" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "retrieveBooking", "arguments": { "pnr": "TEST-ABC123" } }, "id": 1 }' # Returns: Full PNR details (cross-session retrieval works!) ``` ### After 1 Hour: PNR Expired ```bash # Wait 1 hour (or set PNR_TTL_HOURS=0.01 for 36 seconds) sleep 3660 curl -k -X POST https://localhost:8080/mcp \ -H "MCP-Session-ID: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "retrieveBooking", "arguments": { "pnr": "TEST-ABC123" } }, "id": 1 }' # Returns: # { # "error": { # "code": -32001, # "message": "PNR TEST-ABC123 not found or expired" # } # } ``` --- ## Security Recommendations ### Development Environment ✅ **Safe for local development**: - Use on localhost or private networks - Self-signed certificates acceptable - Wildcard CORS appropriate for testing ### Shared Development Server ⚠️ **Use network-level security**: ```bash # Option 1: VPN Access # Deploy server within VPN, require VPN connection # Option 2: Firewall Rules # Allow only specific IP ranges iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j DROP # Option 3: Private Network (AWS/GCP) # Deploy in private subnet, access via bastion host or VPN ``` ### Production-Like Environment ❌ **DO NOT deploy to public internet without authentication**: - Mock server has no authentication (by design for v1) - Wildcard CORS allows any origin - Contains test data but still inappropriate for public exposure **Future Enhancement**: Authentication layer for production deployments --- ## Monitoring & Observability ### Health Check Integration **Kubernetes Liveness Probe**: ```yaml livenessProbe: httpGet: path: /health port: 8080 scheme: HTTPS initialDelaySeconds: 30 periodSeconds: 10 ``` **Prometheus Scraping**: ```yaml scrape_configs: - job_name: 'mcp-server' scheme: https tls_config: insecure_skip_verify: true static_configs: - targets: ['localhost:8080'] metrics_path: '/health' ``` ### Log Aggregation **View structured logs**: ```bash docker-compose logs -f mcp-server | jq ``` **Filter by session**: ```bash docker-compose logs mcp-server | grep "sessionId:abc-123" ``` **Monitor rate limits**: ```bash docker-compose logs mcp-server | grep "RATE_LIMIT_EXCEEDED" ``` --- ## Troubleshooting (Remote Mode) ### Connection Refused ```bash # Check if server is running docker-compose ps # Check if port is exposed netstat -an | grep 8080 # Check nginx logs docker-compose logs nginx ``` ### CORS Errors in Browser ```javascript // Console error: "Access-Control-Allow-Origin" // Check CORS headers: curl -i -H "Origin: https://example.com" https://localhost:8080/mcp # Should include: Access-Control-Allow-Origin: * ``` ### Rate Limit Too Restrictive ```bash # Increase limit in .env RATE_LIMIT_PER_MINUTE=1000 # Restart server docker-compose restart mcp-server ``` ### SSL Certificate Warnings ```bash # Browser: "Your connection is not private" # Expected with self-signed cert # Production: Use Let's Encrypt certbot certonly --standalone -d your-domain.com # Update nginx.conf with real certificate paths ``` ### PNR Not Found (Cross-Session) ```bash # Verify PNR is within TTL window curl https://localhost:8080/health | jq .uptime # If uptime > 3600 and PNR_TTL_HOURS=1, PNR may have expired # Check Valkey directly docker-compose exec valkey valkey-cli > KEYS pnr:* > TTL pnr:TEST-ABC123 ``` --- ## Performance Testing (Remote Mode) ### Load Testing with `ab` (Apache Bench) ```bash # Test health endpoint (10000 requests, 100 concurrent) ab -n 10000 -c 100 -k https://localhost:8080/health # Expected: <2s for 10k requests ``` ### Rate Limit Testing ```bash # Verify rate limiting kicks in ab -n 200 -c 1 https://localhost:8080/health # Should see ~100 successful, ~100 rate limited (429) ``` ### Concurrent Sessions ```bash # Simulate 50 concurrent sessions for i in {1..50}; do ( SESSION=$(uuidgen) curl -k -X POST https://localhost:8080/mcp \ -H "MCP-Session-ID: $SESSION" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' & ) done wait # Check health to see connection count curl -k https://localhost:8080/health | jq .connections ``` --- ## Next Steps (Remote Mode) 1. ✅ Set up Docker Compose with nginx reverse proxy 2. ✅ Generate TLS certificates for development 3. ✅ Configure environment variables for remote access 4. ✅ Test health endpoint and CORS 5. ✅ Verify rate limiting behavior 6. ✅ Test cross-session PNR retrieval 7. ✅ Monitor logs and health metrics 8. 🔄 Integrate with your web-based MCP client 9. 🔄 Deploy to shared development environment (with VPN/firewall) 10. 📋 Plan authentication layer for future production use