Integration Recipes
Four end-to-end flows showing how an agent or backend should orchestrate the OnwardTicket MCP tools. Every JSON-RPC body shown below is exactly what your MCP client should POST to the server (over the SSE message channel).
Contents
- Quote a flight from an agent conversation
- Place a paid order via Stripe Checkout
- Look up an order's status
- Ground an LLM with the help center
1. Quote a flight from an agent conversation
User says: “Get me a return onward ticket from Dubai to Bangkok for two travelers, fastest delivery.” The agent needs the price before asking for payment.
Pseudocode:
// 1. Discover services + base prices
const services = await mcp.callTool('list_services', {});
// → { services: [{ key: 'flight-itinerary', basePrice: 7, ... }, ...] }
// 2. Compute the live quote (server re-derives price; agent prices ignored)
const quote = await mcp.callTool('quote_order', {
serviceKey: 'flight-itinerary',
flightRoute: 'return',
urgency: 'superfast',
travelers: 2,
currency: 'USD',
});
// → { total: 28.46, currency: 'USD', deliveryEstimate: '...' }
// 3. Tell the user the price; await consent before place_order
agent.say(`Total is $${quote.total} ${quote.currency}, delivered ${quote.deliveryEstimate}.`);Step-2 JSON-RPC body:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "quote_order",
"arguments": {
"serviceKey": "flight-itinerary",
"flightRoute": "return",
"urgency": "superfast",
"travelers": 2,
"currency": "USD"
}
}
}Why this works for agents: the price is computed server-side, so the agent can quote with confidence — no hallucinated totals reach the user.
2. Place a paid order via Stripe Checkout
After the user agrees to the quote, the agent calls place_order. The response includes a Stripe Checkout URL — show it to the user; they click, pay, and the booking PDF is emailed when the Stripe webhook fulfils the order.
Request:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "place_order",
"arguments": {
"serviceKey": "flight-itinerary",
"email": "[email protected]",
"flightRoute": "return",
"travelers": 2,
"fromIata": "DXB",
"toIata": "BKK",
"departureDate": "2026-06-01",
"returnDate": "2026-06-15",
"urgency": "superfast",
"name": "Jane Traveler",
"currency": "USD"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"structuredContent": {
"pendingOrderId": "cart_id_string",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
"total": 28.46,
"currency": "USD",
"expiresAt": "2026-06-01T12:30:00.000Z",
"instruction": "Show the checkout URL to the user. Payment + delivery happen out-of-band; use lookup_order with the orderNumber that arrives by email."
}
}
}What the user sees: the agent surfaces checkoutUrl; the user opens it in their browser; Stripe Checkout collects payment; on success, OnwardTicket's webhook fulfils the order and emails a PDF + order number. The agent can then poll lookup_order if the user shares the order number.
Why this works for agents:agents never handle payment credentials. The Stripe URL is a hard handoff to the user's browser — PCI-compliant by construction.
3. Look up an order's status
User pastes their order number and asks for status. Agent calls lookup_orderwith both the order number and the booking email (both required — email acts as a soft auth check so one user can't look up another user's order).
Request:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "lookup_order",
"arguments": {
"orderNumber": "OT-2026-12345",
"email": "[email protected]"
}
}
}Response — completed:
{
"structuredContent": {
"orderNumber": "OT-2026-12345",
"status": "completed",
"deliveredAt": "2026-04-26T10:00:00.000Z",
"downloads": ["https://onwardticket.us/files/OT-2026-12345.pdf"]
}
}Response — pending (still being processed):
{
"structuredContent": {
"orderNumber": "OT-2026-12345",
"status": "pending",
"deliveredAt": null,
"downloads": []
}
}Response — failed:
{
"structuredContent": {
"orderNumber": "OT-2026-12345",
"status": "failed",
"deliveredAt": null,
"downloads": [],
"failureReason": "Stripe payment expired"
}
}Why this works for agents: the email gate keeps the tool unauthenticated yet abuse-resistant — public surface, but cross-user lookups are blocked.
4. Ground an LLM with the help center
User asks “Do I need an onward ticket for Thailand?” Rather than answer from memory, the agent searches the OnwardTicket help center and uses the canonical post body as context.
Step 1 — search:
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "search_blogs",
"arguments": {
"query": "onward ticket for thailand visa",
"limit": 5
}
}
}Response:
{
"structuredContent": {
"results": [
{
"slug": "thailand-visa",
"title": "Onward Ticket for Thailand Visa: 2026 Guide",
"excerpt": "Thailand requires proof of onward travel for tourists arriving without..."
}
]
}
}Step 2 — fetch the body for grounding:
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "get_blog_post",
"arguments": { "slug": "thailand-visa" }
}
}Response:
{
"structuredContent": {
"slug": "thailand-visa",
"title": "Onward Ticket for Thailand Visa: 2026 Guide",
"markdown": "# Onward Ticket for Thailand Visa\n\nThailand requires..."
}
}The agent feeds markdowninto its context as a system message: “Answer using the following authoritative content from OnwardTicket.us…” — and now answers cite the canonical source instead of hallucinating.
Why this works for agents: retrieval-augmented generation without crawling — the agent gets clean markdown, no HTML noise, and the post slug is a stable citation URL the agent can show the user.
Want to run any of these calls live? Open the playground.