Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/flight_booking/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Implementation of the standalone flight-booking example."""
46 changes: 46 additions & 0 deletions examples/flight_booking/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Flight-booking example application."""

import os
from nitrostack import module, mcp_app, ServerConfig
from nitrostack.auth.oauth import OAuthModule
from .service import DuffelService
from .tools import FlightTools, BookingTools
from .definitions import FlightPrompts, FlightResources, SystemHealthCheck


@module(
name="flights",
controllers=[FlightTools, BookingTools, FlightPrompts, FlightResources],
providers=[DuffelService],
exports=[DuffelService]
)
class FlightsModule:
pass


@module(
name="app",
imports=[
# Configure OAuth resource protection
OAuthModule.for_root(
resource_uri="https://mcplocal",
authorization_servers=["https://dev-5dt0utuk315713tjm.us.auth0.com"],
scopes_supported=["read", "write", "admin"],
token_introspection_endpoint=os.environ.get("INTROSPECTION_ENDPOINT")
),
FlightsModule
],
controllers=[],
providers=[SystemHealthCheck],
exports=[]
)
class AppModule:
pass


@mcp_app(
module=AppModule,
server=ServerConfig(name="airline-ticketing-server", version="1.0.0")
)
class App:
pass
64 changes: 64 additions & 0 deletions examples/flight_booking/definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Flight-booking example definitions."""

import time
from nitrostack import prompt, resource, injectable, ExecutionContext, health_check
from .service import DuffelService


@injectable(deps=[DuffelService])
class FlightPrompts:
def __init__(self, service: DuffelService):
self.service = service

@prompt(
name="flight_search_assistant",
description="An AI assistant specialized in helping users search for flights and make booking decisions."
)
async def flight_search_assistant(self, args: dict, context: ExecutionContext) -> str:
return "You are a professional flight booking assistant. Help the user search for flights using search_flights, search_airports, and assist in booking holds."

@prompt(
name="flight_comparison",
description="Compare multiple flight offers and provide recommendations."
)
async def flight_comparison(self, args: dict, context: ExecutionContext) -> str:
return "Compare the provided flight offer details, check layover times, durations, and pricing, and give the user a summary of best options."


@injectable(deps=[DuffelService])
class FlightResources:
def __init__(self, service: DuffelService):
self.service = service

@resource(
uri="flight://popular-routes",
name="Popular Flight Routes",
description="Information about popular routes and pricing",
mime_type="application/json"
)
async def popular_routes(self, context: ExecutionContext) -> dict:
return {
"routes": [
{"route": "JFK -> LAX", "price": "$200-400"},
{"route": "LHR -> JFK", "price": "$400-800"}
]
}

@resource(
uri="flight://booking-guide",
name="Flight Booking Guide",
description="Guide on searching and booking flights",
mime_type="text/markdown"
)
async def booking_guide(self, context: ExecutionContext) -> str:
return "# Flight Booking Guide\n\n1. Search flights.\n2. Collect passenger info.\n3. Create order hold."


class SystemHealthCheck:
def __init__(self):
self.start_time = time.time()

@health_check("system")
def check_system(self) -> bool:
uptime = time.time() - self.start_time
return uptime >= 0
17 changes: 17 additions & 0 deletions examples/flight_booking/guards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Flight-booking example guards."""

from nitrostack import ExecutionContext


def create_scope_guard(required_scopes: list):
class ScopeGuard:
async def can_activate(self, context: ExecutionContext) -> bool:
user_scopes = getattr(context.auth, "scopes", [])
missing_scopes = [s for s in required_scopes if s not in user_scopes]
if missing_scopes:
raise ValueError(
f"Insufficient scope. Required: {', '.join(required_scopes)}. "
f"Missing: {', '.join(missing_scopes)}"
)
return True
return ScopeGuard
42 changes: 42 additions & 0 deletions examples/flight_booking/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Flight-booking example models."""

from typing import Optional
from pydantic import BaseModel, Field


class SearchFlightsInput(BaseModel):
origin: str = Field(description="Origin airport IATA code (e.g., 'JFK', 'LHR')")
destination: str = Field(description="Destination airport IATA code (e.g., 'LAX', 'CDG')")
departureDate: str = Field(description="Departure date in YYYY-MM-DD format")
returnDate: Optional[str] = Field(default=None, description="Return date in YYYY-MM-DD format for round trip")
adults: int = Field(default=1, description="Number of adult passengers (18+)")
cabinClass: str = Field(default="economy", description="Preferred cabin class (economy, premium_economy, business, first)")


class FlightDetailsInput(BaseModel):
offerId: str = Field(description="The flight offer ID from search results")


class AirportSearchInput(BaseModel):
query: str = Field(description="The search query for airports (e.g., 'London', 'New York')")


class CreateOrderInput(BaseModel):
offerId: str = Field(description="The offer ID to book")
passengers: str = Field(description="JSON string containing array of passenger objects. Each passenger must have: title (mr/ms/mrs/miss/dr), givenName (first name), familyName (last name), gender (M/F), bornOn (YYYY-MM-DD), email, phoneNumber.")


class OrderDetailsInput(BaseModel):
orderId: str = Field(description="The order ID")


class SeatMapInput(BaseModel):
offerId: str = Field(description="The offer ID to get seats for")


class CancelOrderInput(BaseModel):
orderId: str = Field(description="The order ID to cancel")


class GetAirlinesInput(BaseModel):
pass
Loading
Loading