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.

EnvironmentURLAccessBookings
Sandboxhttps://staging.mcp.cyah.ai/sandbox/mcpOpen, no key needed. Without pricesTest mode: Stripe test cards, nothing is really booked
Productionhttps://mcp.cyah.ai/mcpAPI 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

  1. Searchsearch_destinations → search_hotels / search_flights / search_trains
  2. Chooseget_hotel_offer for rooms and rates
  3. Booking linkprepare_booking / prepare_flight_booking / prepare_train_booking → booking_url
  4. Pay on cyah.aiThe traveller enters details and pays securely via Stripe

Quickstart

The sandbox needs no key. Paste this into a terminal:

curl
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).

Response
{
  "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:

Python · requests
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)

Python · mcp
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)

TypeScript · @modelcontextprotocol/sdk
// 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.

Python · openai
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:

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 keyOAuth 2.0 client credentials
ForSimple server-to-server integrationsPartners (recommended)
HeaderAuthorization: Bearer ymcp_prd_…Authorization: Bearer <access_token>
ValidityUntil revoked or the agreed end dateToken 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.

OAuth 2.0 · client_credentials
# 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

SandboxProduction
Tool calls per day200 per IP address, 3,000 for all developers togetherAgreed per access
Requests per 5 minutes1,000 per IP address600 per key/token, 1,000 per IP address
DurationUsually 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:

HTTPMeaning
401Missing or invalid key/token (production)
403Access revoked or expired
405POST only: the server is stateless, there is no GET stream
406Accept header lacks application/json, text/event-stream
429Too 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.

Tool error
{
  "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 a booking_url to 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 (nl or en): 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.