fixing jsonSchema validation by using zod
This commit is contained in:
@@ -506,3 +506,571 @@ Verify you're retrieving from the same session that created the booking.
|
||||
- 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user