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>
This commit is contained in:
508
specs/001-mock-gds-server/quickstart.md
Normal file
508
specs/001-mock-gds-server/quickstart.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# 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`)
|
||||
Reference in New Issue
Block a user