cyah.ai › Developers
cyah MCP · developers
Build with the cyah MCP server
Live hotels, flights and trains for your AI assistant or app, through one open standard. Try the tools right away in the sandbox, no account needed.
Overview
The cyah MCP server gives AI assistants and apps access to the same travel data as the assistant on cyah.ai: live availability and prices of hotels, flights and trains, and booking links where a traveller pays on cyah.ai. The server speaks the open Model Context Protocol (MCP), so any MCP client can use it: Claude, ChatGPT, the OpenAI Responses API, LangChain, n8n or your own code.
search_destinations · search_hotels · get_hotel_offer · find_hotel · prepare_bookingFlights
Flights from hundreds of airlines, seats and extras, booking.
search_flights · prepare_flight_bookingTrains
Trains across Europe, seat preference, booking.
search_train_stations · search_trains · prepare_train_booking| Environment | URL | Access | Bookings |
|---|---|---|---|
| Sandbox | https://staging.mcp.cyah.ai/sandbox/mcp | Open, no key needed. Without prices | Test mode: Stripe test cards, nothing is really booked |
| Production | https://mcp.cyah.ai/mcp | API key or OAuth 2.0 (on request) | Live: the traveller pays and books on cyah.ai |
Transport: Streamable HTTP, stateless. Every request is a POST with a JSON-RPC message; the response is plain JSON (no session, no SSE stream needed). Supported methods: initialize, tools/list and tools/call.
How a booking works
- Search
search_destinations→search_hotels/search_flights/search_trains - Choose
get_hotel_offerfor rooms and rates - Booking link
prepare_booking/prepare_flight_booking/prepare_train_booking→booking_url - Pay on cyah.aiThe traveller enters details and pays securely via Stripe
Quickstart
The sandbox needs no key. Paste this into a terminal:
curl -s https://staging.mcp.cyah.ai/sandbox/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search_destinations","arguments":{"query":"Lisbon"}}}'The result is in result.structuredContent (as an object) and in result.content[0].text (the same JSON as text, for LLM clients).
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "{\"query\":\"Lisbon\",\"hotel_places\":[…]}" }],
"structuredContent": { "query": "Lisbon", "hotel_places": [ … ], "airports": [ … ] },
"isError": false
}
}A complete search in Python, without an SDK:
import requests # pip install requests
URL = "https://staging.mcp.cyah.ai/sandbox/mcp" # production: https://mcp.cyah.ai/mcp
HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
# "Authorization": "Bearer ymcp_prd_...", # production only
}
def call_tool(name, arguments):
body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": name, "arguments": arguments}}
result = requests.post(URL, json=body, headers=HEADERS, timeout=90).json()["result"]
if result["isError"]:
raise RuntimeError(result["structuredContent"]["error"])
return result["structuredContent"]
place = call_tool("search_destinations", {"query": "Lisbon", "types": "hotel"})["hotel_places"][0]
hotels = call_tool("search_hotels", {"place_id": place["place_id"], "check_in": "2026-12-11",
"check_out": "2026-12-13", "adults": 2, "sort": "price"})
for h in hotels["hotels"][:3]:
print(h["hotel_id"], h["name"], h["stars"], h.get("total_price", "(no prices in the sandbox)"))Mind the Accept header: the server requires application/json, text/event-stream (MCP specification), otherwise you get 406 Not Acceptable.
Connect a client
Official MCP SDK (Python)
import asyncio
from mcp import Client # pip install mcp (official MCP Python SDK)
async def main():
async with Client("https://staging.mcp.cyah.ai/sandbox/mcp") as client:
tools = await client.list_tools()
print([t.name for t in tools.tools])
res = await client.call_tool("search_destinations", {"query": "Lisbon"})
print(res.structured_content["hotel_places"][0])
asyncio.run(main())Official MCP SDK (TypeScript / Node.js)
// npm install @modelcontextprotocol/sdk
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const transport = new StreamableHTTPClientTransport(new URL('https://staging.mcp.cyah.ai/sandbox/mcp'));
// production: new StreamableHTTPClientTransport(new URL('https://mcp.cyah.ai/mcp'),
// { requestInit: { headers: { Authorization: `Bearer ${process.env.CYAH_MCP_KEY}` } } });
const client = new Client({ name: 'my-travel-app', version: '1.0.0' });
await client.connect(transport);
const { tools } = await client.listTools();
console.log(tools.map((t) => t.name));
const res = await client.callTool({ name: 'search_train_stations', arguments: { query: 'Paris', limit: 3 } });
console.log(res.structuredContent.results);
await client.close();OpenAI Responses API
OpenAI calls the MCP server itself; the model picks the tools.
from openai import OpenAI # pip install openai
client = OpenAI()
resp = client.responses.create(
model="gpt-4.1-mini",
tools=[{
"type": "mcp",
"server_label": "cyah",
"server_url": "https://staging.mcp.cyah.ai/sandbox/mcp",
# production: "server_url": "https://mcp.cyah.ai/mcp", "headers": {"Authorization": "Bearer ymcp_prd_..."}
"require_approval": "never",
}],
input="Find a 3-star hotel in Lisbon from 11 to 13 December for 2 adults and give me a booking link.",
)
print(resp.output_text)Claude Desktop
Through the mcp-remote bridge in claude_desktop_config.json:
{
"mcpServers": {
"cyah-sandbox": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://staging.mcp.cyah.ai/sandbox/mcp"]
},
"cyah": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.cyah.ai/mcp",
"--header", "Authorization:Bearer ${CYAH_MCP_KEY}"],
"env": { "CYAH_MCP_KEY": "ymcp_prd_..." }
}
}
}MCP Inspector
Handy for testing tools by hand: npx @modelcontextprotocol/inspector, choose transport Streamable HTTP and enter the sandbox URL. Or use the playground.
cyah in ChatGPT. For end users cyah is coming as an app in ChatGPT. This page is about integrating from your own product.
Authentication
The sandbox is open. For production you get your own access with your own limits after a request. There are two kinds:
| API key | OAuth 2.0 client credentials | |
|---|---|---|
| For | Simple server-to-server integrations | Partners (recommended) |
| Header | Authorization: Bearer ymcp_prd_… | Authorization: Bearer <access_token> |
| Validity | Until revoked or the agreed end date | Token 60 minutes; then request a new one |
Scopes decide which tools you see: search gives the 7 search tools, booking adds the 3 prepare_* tools. tools/list only shows what your access may call.
# 1. Get an access token (valid 60 minutes) with your client id and secret
curl -s -X POST https://youmalou-mcp-prd-448759861986.auth.eu-west-1.amazoncognito.com/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&scope=youmalou-mcp/search youmalou-mcp/booking'
# 2. Use it as a Bearer token
curl -s https://mcp.cyah.ai/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Without or with an invalid token the server answers 401 with a WWW-Authenticate header pointing to /.well-known/oauth-protected-resource (RFC 9728).
Keep keys and secrets server-side only, never in a browser or app.
Limits
| Sandbox | Production | |
|---|---|---|
| Tool calls per day | 200 per IP address, 3,000 for all developers together | Agreed per access |
| Requests per 5 minutes | 1,000 per IP address | 600 per key/token, 1,000 per IP address |
| Duration | Usually 1 to 10 seconds; a hotel search can take up to about 20 seconds. Use a timeout of at least 60 seconds. | |
Above the 5-minute limit you get HTTP 429. When the daily limit is used up, the tool returns an error with the reason (see below). Hotel search results are cached for 15 minutes; get_hotel_offer and the prepare_* tools always use live prices.
Errors
There are two kinds of errors. Protocol and access errors get an HTTP status code:
| HTTP | Meaning |
|---|---|
401 | Missing or invalid key/token (production) |
403 | Access revoked or expired |
405 | POST only: the server is stateless, there is no GET stream |
406 | Accept header lacks application/json, text/event-stream |
429 | Too many requests in 5 minutes |
Tool errors (invalid input, no availability, supplier unreachable, daily limit) come back as a normal result with isError: true and a readable error. An LLM can use that text directly to correct its call.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "{\"error\":\"check_out must be after check_in\"}" }],
"structuredContent": { "error": "check_out must be after check_in" },
"isError": true
}
}Booking and payment
- Your product never handles payment, passport or personal data. The
prepare_*tools return abooking_urlto the secure booking page on cyah.ai; the traveller enters details there and pays via Stripe. - Price and availability are checked at
prepare_*and again on the booking page. The booking is only final after payment, so never say something is booked when only the link was created. - Hotel, flight and train can go into one cart and be paid in one go.
- After booking, the traveller manages changes and cancellations via My booking on cyah.ai.
- Pass
language(nloren): texts and the booking page follow that language.
Request production access
Does your integration work in the sandbox? E-mail hello@cyah.ai with your company name, a short description of your use case, the expected number of calls per day and whether you also want to book (scope booking). You will receive an API key or OAuth client through a secure channel.
Changes to tools are backwards compatible: new fields and optional parameters can be added, existing ones are not removed without notice. So do not treat unknown fields as errors.