MCP Server
Connect StayAPI to Claude, Claude Code, Cursor, and other Model Context Protocol clients. Your AI agent queries hotel data directly from natural-language prompts — no glue code.
User:
"Get me the latest reviews for booking.com/hotel/th/baan-coconut.html"
Agent:
→ calls booking_hotel_reviews(url=…) → returns structured reviews
What you get
-
No new credentials. Sign in with your StayAPI account from Claude or ChatGPT, or use your existing
X-API-Key. - Same quota and billing as the REST API — one tool call = one API request.
- Same SLA — calls run on the same infrastructure as the REST API.
- Works in Claude (web, desktop & mobile), Claude Code, Cursor out of the box.
- Works in ChatGPT as an MCP app in developer mode (setup below).
Quick Start
Fastest: let your agent install it
Paste the prompt into the agent you already have open. It reads stayapi.com/install and sets up the connector itself.
Read https://stayapi.com/install and set up the StayAPI MCP server for me.
Then open a new session and run claude mcp list — stayapi should read ✓ Connected.
Add to Cursor in one click
Installs with a YOUR_API_KEY placeholder you swap in Cursor.
Read https://stayapi.com/install and set up the StayAPI MCP server for me.
The agent writes ~/.cursor/mcp.json, then asks you to restart Cursor.
Read https://stayapi.com/install and set up the StayAPI MCP server for me.
Codex reads the page and writes its own MCP config.
Read https://stayapi.com/install and set up the StayAPI MCP server for me.
Gemini CLI, Windsurf, or anything else that speaks MCP — the agent adds a remote HTTP server with the X-API-Key header to that client's own config.
No key yet? Sign up for 50 free requests — the agent asks you for it.
Claude on the web, desktop, or mobile doesn't need this prompt: add StayAPI under Settings → Connectors and sign in. ChatGPT: add it as an MCP app in developer mode and sign in. Other clients: manual setup below.
Rather do it by hand? The full walkthrough follows.
Have an account? Use the pre-filled setup
Get your API key
Sign up for a free StayAPI account (no credit card required), then copy your API key from the dashboard.
Already have an account? Sign in.
Add the MCP server to your agent
Pick the snippet for your AI client
mcp-remote bridge because its JSON config doesn't accept HTTP servers directly.
Claude (web, desktop & mobile)
No API key needed — you sign in to StayAPI instead. Custom connectors sync across Claude on the web, desktop, and mobile.
- In Claude, open Settings → Connectors → Add custom connector.
- Name it
StayAPIand set the URL tohttps://api.stayapi.com/mcp. Leave the advanced OAuth fields empty. - Click Connect, sign in to StayAPI, and choose Allow.
ChatGPT (developer mode)
No API key needed — you sign in to StayAPI instead. StayAPI isn't in ChatGPT's plugin directory yet, so you add it yourself in developer mode (Plus, Pro, Business, Enterprise, or Edu, on the web).
- In ChatGPT, turn on Settings → Security and login → Developer mode.
- Open
chatgpt.com/plugins, click +, and choose Create MCP app. - Name it
StayAPI, set the URL tohttps://api.stayapi.com/mcp, and pick OAuth. Leave the client ID and secret blank. - Sign in to StayAPI and choose Allow, then install StayAPI from your personal plugins (
chatgpt.com/plugins?view=personal). - In a Work chat, type
@StayAPIand ask.
ChatGPT can show results as hotel cards (the free show_hotels tool). Data tool calls use your plan's credits, like API-key calls.
Claude Code
Run from your terminal:
claude mcp add --scope user --transport http stayapi https://api.stayapi.com/mcp --header "X-API-Key: YOUR_API_KEY"
Then open a new Claude Code session (already-running sessions won't see the new server). Confirm with claude mcp list — should show stayapi: … (HTTP) - ✓ Connected.
Cursor
One click installs the connector in Cursor:
Add to Cursor
After installing, replace YOUR_API_KEY in Cursor's MCP settings with your real key.
Or sign in for a one-click button with your key already baked in.
Prefer to set it up by hand? Add to ~/.cursor/mcp.json (create if missing), then restart Cursor:
{
"mcpServers": {
"stayapi": {
"type": "http",
"url": "https://api.stayapi.com/mcp",
"headers": {
"X-API-Key": "YOUR_API_KEY"
}
}
}
}
Claude Desktop
Add to your claude_desktop_config.json (create if missing):
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
macOS / Linux:
{
"mcpServers": {
"stayapi": {
"command": "npx",
"args": [
"-y",
"mcp-remote@0.1.18",
"https://api.stayapi.com/mcp",
"--header",
"X-API-Key:YOUR_API_KEY"
]
}
}
}
Windows:
Claude Desktop doesn't inherit your shell PATH, so a bare npx throws spawn npx ENOENT. Point command at the absolute path to npx.cmd:
{
"mcpServers": {
"stayapi": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"mcp-remote@0.1.18",
"https://api.stayapi.com/mcp",
"--header",
"X-API-Key:YOUR_API_KEY"
]
}
}
}
- Verify Node in Command Prompt (not the Node.js app / REPL) with
where npx. If it points elsewhere, use that path; the default isC:\Program Files\nodejs\npx.cmd. - Keep
mcp-remote@0.1.18pinned — newer builds can throwUnexpected content type: null.
- Fully quit Claude Desktop with ⌘ Q — closing the window leaves it running in the menu bar and the new server won't load.
- Relaunch. First launch may take ~30 seconds while
npxdownloadsmcp-remotefrom npm; subsequent launches are instant. On a fully locked-down machine, IT may need to allowregistry.npmjs.org. - In a new conversation, click the 🔌 icon —
stayapishould appear.
Why mcp-remote? Claude Desktop's JSON config doesn't natively support HTTP MCP servers yet — it requires stdio entries (command/args), and the mcp-remote adapter bridges the gap. Prefer not to edit JSON? Add StayAPI as a Claude connector instead (see above) — it signs in with OAuth and needs no key.
Try it
Ask your agent something like:
- "What's the destination ID for Lisbon on Booking.com?"
- "Get me 10 of the most recent reviews for https://www.booking.com/hotel/th/baan-coconut.html"
- "Look up Google reviews for 'Bellagio Las Vegas'"
- "Get exact Radisson room rates in London for two nights and separate public from member offers"
Available tools
Most agents will discover and call the right tool automatically based on your prompt — you don't need to memorize tool names.
| Tool | What it does |
|---|---|
booking_lookup_destination |
Free-form name (e.g., "Lisbon") → Booking.com dest_id. First step before booking_search_hotels when you only have a city name. |
booking_search_hotels |
List hotels in a destination for a stay window. Takes a dest_id (negative integers like -3233180 are valid — preserve the sign). country_market (default US) sets the pricing market — Booking.com prices by the visitor's country. Optional market selects country and language/currency defaults (ES: es/EUR; US: en-us/USD); explicit overrides win. effective_market reports best-effort targeting, not verified geography. Price objects include labels, genius_discount_pct, and is_mobile_rate for observed anonymous rate markers. |
booking_hotel_details |
Get one hotel's details — including description and check-in/check-out times — by hotel_id (fast) or canonical Booking URL (slower — the URL is resolved to an ID first). |
booking_hotel_rooms |
Get room types and live availability for one hotel by hotel_id or URL, for a stay window. Returns bookable rate blocks plus availability_summary.total_available_rooms — a deduplicated count across rate plans. Optional market sets country and language/currency defaults; explicit overrides win. effective_market reports unverified targeting. Pass the same country_market as the search for matching prices. children_ages is a comma-separated string of integer ages 0–17; its count must equal children. Rate price objects include labels, genius_discount_pct, and is_mobile_rate. |
booking_hotel_reviews |
Get reviews for a hotel by hotel_id or URL, with sort and pagination. Use sort=recent_desc for date-bounded backfills. |
booking_hotel_photos |
Get a hotel's full photo gallery by hotel_id or URL. Returns signed, ready-to-use image URLs at thumbnail, medium, and large sizes. limit (default 20, 0 = all) and size (thumbnail / medium / large / all) keep responses compact; metadata.photo_count is always the full gallery size. |
airbnb_listing_details |
Get an Airbnb listing's details by a decimal-string listing_id or any Airbnb URL (URL→ID is instant). |
airbnb_listing_calendar |
Get calendar availability by integer listing_id or string url; provide at least one, with listing_id taking precedence. Integer months defaults to 12 (range 1–12). Returns listing_id and months, each with month, year, and days containing date, available, available_for_checkin, bookable, and min_nights. No nightly pricing. Each successful call costs one credit. |
airbnb_listing_reviews |
Get reviews for an Airbnb listing by a decimal-string listing_id or URL, with sort + pagination. |
google_hotels_search |
Search hotels via Google Hotels (SerpAPI-backed). |
google_reviews_for_place |
Get Google reviews for a place — by Google data_id or by query + optional location. |
google_travel_resolve |
Hotel name → Google Travel entity_token candidates. First step for the other google_travel_* tools. |
google_travel_search |
Free-text hotel search ("hotels in Lisbon") → ~20 hotel cards per page, each with rating, price, star class, coordinates, check-in/out times, description, hero images, opaque amenity flags, Google hotel_id, and an entity_token for drilldown. Supports optional dates, adults, currency, and Google's 3.5+, 4.0+, or 4.5+ guest-rating filter. |
google_travel_summary |
A hotel's review summary by entity_token: overall rating, total count, star histogram, and review-topic sub-scores. |
google_travel_info |
A hotel's identity/metadata by entity_token: name, star class, coordinates, address, phone, check-in/out times, descriptions, website, Maps/directions links and ids, country code, and opaque amenity flags. |
google_travel_photos |
A hotel's photo gallery by entity_token (https URLs, dimensions, and occasional AI caption highlights). |
google_travel_prices |
A hotel's nightly rate, usual-price band, and partner booking offers by entity_token; optional paired YYYY-MM-DD dates plus adults target an explicit stay. Check-in must be today or later and check-out must be after check-in; otherwise Google chooses dates. currency shapes money fields. |
google_travel_similar |
Similar hotels + vacation rentals near a hotel (entity_token), each with its own token to pivot to. |
google_travel_nearby |
Nearby points of interest (attractions, transit, airports, restaurants) with ratings and drive/walk times, by entity_token. |
google_travel_reviews |
A hotel's individual Google reviews by entity_token — rating, text, reviewer, owner response — with sort (most_relevant / newest / highest_rating / lowest_rating) and cursor pagination. |
accor_search |
Search Accor (ALL — Sofitel, Fairmont, Novotel, Ibis, etc.) hotels by latitude+longitude (+radius_km) or destination_slug, with each property's best available offer. |
accor_hotel_details |
Get an Accor property's public content by hotel_id: contact and location, descriptions, ratings, room names and features, facilities, payment methods, accessibility, and media previews. |
accor_hotel_amenities |
Get detailed amenity categories and services by hotel_id, including fee, coverage, and location metadata plus connecting and family room inventory. |
accor_hotel_reviews |
Get up to 20 Accor guest reviews by hotel_id, including ratings, text, trip type, dates, and hotel responses. |
accor_hotel_rooms |
Get bookable room offers for an Accor property by hotel_id (from accor_search), grouped into room types with rate plans, prices, and policies. |
accor_hotel_photos |
Get an Accor property's full photo/video gallery by hotel_id. Public image URLs at every published size. |
accor_price_calendar |
Get a per-date cheapest-offer calendar for an Accor property by hotel_id (window up to 60 days). |
radisson_search |
Search Radisson by destination or hotel name. Returns hotel ids and public property metadata; prices are not included. |
radisson_hotel_details |
Get identity, description, location, contact details, services, and status for one Radisson hotel_id. |
radisson_hotel_amenities |
Get structured services, property types, location types, and tags. |
radisson_hotel_reviews |
Get Radisson's TripAdvisor aggregate, ranking, traveler breakdowns, and published review sample. |
radisson_hotel_url_to_id |
Resolve a canonical radissonhotels.com/.../hotels/{slug} URL to its Radisson hotel code. |
radisson_hotel_rooms |
Get static room names, descriptions, capacity, services, and media. Live availability is deliberately excluded. |
radisson_hotel_photos |
Get the full categorized Radisson property gallery with public image URLs. |
radisson_price_calendar |
Get the lowest public price per arrival date across a window of up to 60 days. |
radisson_hotel_rates |
Get the live lowest available cash rate for one hotel and stay, with optional member pricing. |
radisson_hotel_room_rates |
Get raw public/member offers plus normalized_rates for single-room cash alternatives, nightly reward quotes, and points_total. Final redemption cash payable is unverified; mixed cash-and-points offers remain in raw data. |
tripadvisor_geo_search |
Free-form place name (e.g., "Paris") → TripAdvisor geo_id. First step before tripadvisor_search_hotels if you only have a place name. |
tripadvisor_search_hotels |
List TripAdvisor hotels in a geo_id area for an optional stay window. Each hotel carries a location_id for the other TripAdvisor tools. |
tripadvisor_hotel_details |
Get one TripAdvisor hotel's details by location_id or any TripAdvisor hotel URL (URL→ID is instant). |
tripadvisor_hotel_reviews |
Get reviews for a TripAdvisor hotel by location_id or URL, with page/per_page and language. At most 20 reviews per page; per_page above 20 is treated as 20. |
tripadvisor_hotel_prices |
Get live provider offers (Booking.com, Agoda, etc.) for a TripAdvisor hotel by location_id or URL for a required stay window. |
agoda_hotel_url_to_id |
An agoda.com/<slug>/hotel/<city>-<country>.html URL (an /all/ segment is also accepted) → the numeric Agoda hotel_id. First step before the other Agoda tools. It fetches the hotel page, so it costs one quota unit. |
agoda_hotel_prices |
Get live dated Agoda room availability by hotel_id, including the lowest all-inclusive stay price, per-night and whole-stay taxes/fees, breakfast, cancellation terms, and explicit sold-out status. Optional children_ages is a JSON integer array of up to nine ages 0–17 and overrides children, including [] for adults only. Omit it to use age 8 for each child in children. |
agoda_hotel_reviews |
Get guest reviews for one Agoda hotel by hotel_id, aggregated across the providers Agoda syndicates. Agoda always serves 20 per page. sorting is 7 = most recent (default), 6 = highest rating, 5 = lowest rating. |
agoda_hotel_review_comments |
Get an Agoda hotel's reviews from one review provider instead of the cross-provider aggregate. Same shape and 20-per-page pagination as agoda_hotel_reviews; provider_id picks the source and defaults to 3038 (Agoda's own reviews). |
otelpuan_hotel_rooms |
Get rooms and prices for a hotel url, check_in, check_out, adults (default 2), and optional comma-separated child_ages. Returns board plans, source stay totals, daily prices, cancellation terms, and occupancy. Reviews continue to use hotel_id. |
otelpuan_search_hotels |
Find hotel suggestions by query (2–200 characters after trimming). Returns hotel_id, name, URL, and nullable location. Use hotel_id directly for reviews. Not exhaustive city inventory; no pagination or filters. |
otelpuan_hotel_url_to_id |
Resolve a public Otelpuan hotel URL to hotel_id, name, canonical URL, aggregate rating, and review count. Pass the returned hotel_id to otelpuan_hotel_reviews. |
otelpuan_hotel_reviews |
Get Otelpuan guest reviews by hotel ID. Supply hotel_id and optional page (default 1); each page contains up to 10 reviews. Ratings use a 0–10 scale. Stay dates are separate from nullable publication dates. |
holidaycheck_hotels_search |
Find hotels by query (1–200 characters after trimming), with page default 1 and up to 10 matches per page. available_count and has_more describe the available result set, not a global total. Pass returned hotel_id directly to details or reviews. No dates, prices, or destination search. |
holidaycheck_hotel_details |
Read hotel details by hotel_id UUID: canonical URL, address, location, contact, descriptions, grouped amenities, and check-in/out wording. Star classification is separate from native six-point guest ratings. No prices or photo gallery. Each successful call costs one credit. |
holidaycheck_hotel_url_to_id |
Extract a lowercase hotel UUID from a supported HolidayCheck hotel or review-listing URL. Does not verify existence; individual /hrd/ review URLs are excluded. |
holidaycheck_hotel_reviews |
Read HolidayCheck reviews by hotel_id UUID. Optional page defaults to 1; sort is most_relevant (default) or recent_desc. Returns up to 10 reviews per page with native six-point ratings, text sections, and recommendation percentage. No language control or owner responses. Each successful call costs one credit. |
tripcom_hotel_reviews |
Get paginated Trip.com guest reviews by hotel_id — rating, text, stay date, room type, travel type, and the hotel's owner response. page_size is 1–50 (default 10); currency and locale only localize the response. Drives a real browser, so expect several seconds per call. |
makemytrip_search_hotels |
Search MakeMyTrip hotels in India by city_code, dates, and occupancy. Returns hotel IDs, INR listing prices, tax and coupon fields, photos, review summaries, and an opaque next_cursor. |
makemytrip_get_hotel_details |
Get MakeMyTrip hotel descriptions, address, coordinates, amenities, arrival times, and photos. Supply hotel_id, city_code, check_in, and check_out. |
makemytrip_get_hotel_reviews |
Get MakeMyTrip review text, ratings, stay context, and source-aware pagination. Only Most relevant sorting is supported; stop automatic pagination when source_changed is true and next_start is null. |
hilton_rooms |
Get dated Hilton room types, starting cash offers, and standard or premium full-points rewards. Use hotel_code, dates, 1–4 adults, and USD or GBP; one room with no children. Reward points include exact stay totals and dated nightly breakdowns. No Points & Money quotes. |
hilton_search |
Search undated Hilton hotel metadata in English by query or coordinates. Returns hotel_code, photos, amenities, and opening status, without prices or stay availability. |
hilton_hotel_url_to_id |
Extract the seven-character Hilton hotel_code (ctyhocn) from a canonical HTTPS Hilton hotel or reservation rooms URL without checking existence. |
bestwe_search_hotels |
Search dated WeHotel (Jin Jiang) inventory by city_code. Returns property IDs, public metadata, availability, and lowest visible CNY prices. |
bestwe_hotel_url_to_id |
Extract a WeHotel (Jin Jiang) property_id locally from a canonical hotel.bestwehotel.com/HotelDetail?hotelId=... URL. |
bestwe_hotel_details |
Get bilingual WeHotel (Jin Jiang) property content, policies, photos, facilities, services, and static room metadata. |
bestwe_hotel_reviews |
Get paginated WeHotel (Jin Jiang) guest reviews with scores, moderated photos, tags, room context, and hotel replies. |
bestwe_hotel_rooms |
Get dated WeHotel (Jin Jiang) room inventory and public or member rate plans with CNY prices, breakfast, and cancellation rules. |
expedia_hotel_rates |
Get Expedia room rates and availability by numeric property_id and ordered nonpast stay dates. Adults 1–10; optional children_ages is a JSON integer array of up to six ages 0–17. Omit it or use [] for adults-only rates. Currency defaults to USD; conversion for other three-letter codes is not confirmed. Sold-out stays are valid results. Available results include at least one priced offer; malformed availability is an error. Expedia discovery and reviews use REST. |
marriott_bonvoy_search |
Search Marriott Bonvoy hotels near a latitude+longitude for a stay. Returns each property's property_id, brand, rating, distance, lowest cash price, and points_per_night when an award rate exists; up to 20 per page, optional corporate code. |
marriott_bonvoy_rooms |
Get live room types and rate plans for a Marriott property_id and stay: cash per_night and total, plus Bonvoy award pricing with points (check-in night), points_total (whole stay), and a per-night nightly_rates breakdown. |
opentable_search_restaurants |
Search OpenTable restaurants near a latitude+longitude for a date/time/party_size. Returns name, cuisine, price band, rating, address, phone, and photos. |
opentable_restaurant_url_to_id |
Resolve a full OpenTable /r/{slug} profile URL to its numeric restaurant_id, name, country, macro area, and neighborhood. |
opentable_restaurant_details |
Get one OpenTable restaurant profile by restaurant_id, including cuisine, ratings, address, coordinates, contact details, hours, photos, policies, dining areas, and feature flags. |
opentable_restaurant_menu |
Get an OpenTable restaurant's published menus by restaurant_id: menu and section names, item descriptions, prices, and portion variations. Prices are strings (restaurants can publish values like MKT); a restaurant with no structured menu returns an empty menus array. |
opentable_restaurant_reviews |
Get OpenTable reviews by restaurant_id, sorted by newest, highest, or lowest. Returns up to 25 reviews per page. |
opentable_restaurant_availability |
Check whether one exact reservation time is available for a restaurant, date, and party size. |
opentable_restaurant_availability_slots |
Return OpenTable's nearby bookable HH:MM slots around a preferred_time. |
opentable_private_dining_restaurants |
Search OpenTable private-dining venues near a latitude+longitude, with cuisine_ids/instant_book/radius/min_capacity filters. Returns the private-dining contact (name/phone/email), largest-space capacity, plus cuisine, rating, and photos. Pass with_facets=true to discover cuisine IDs. |
meta_coordinates_lookup |
Resolve a place name (e.g. "Manhattan") to coordinates. Returns matching locations with latitude/longitude, type, country, and place_id — use it to get the lat/lng the other tools need. |
airbnb_search_listings |
First-page Airbnb destination listings with optional paired dates and guest counts. Undated results omit prices; neither mode verifies availability. No property-name lookup or pagination. |
URL vs ID
For Booking, Airbnb, and TripAdvisor tools that fetch a single item, you can pass either an id or a url. For OpenTable, call opentable_restaurant_url_to_id once, then pass its restaurant_id to details, menus, reviews, or availability.
Radisson keeps resolution explicit. Call radisson_hotel_url_to_id, then pass the returned hotel_id to the content or pricing tool.
Agoda and WeHotel (Jin Jiang) do the same. Call agoda_hotel_url_to_id or bestwe_hotel_url_to_id, then pass the returned ID to the content, review, or room tools. Trip.com has no resolver, so tripcom_hotel_reviews takes the numeric hotel_id only. Marriott Bonvoy has no URL resolver either: marriott_bonvoy_search returns the property_id codes that marriott_bonvoy_rooms takes.
- ID is faster — no resolution step needed.
- URL works too — the server resolves it internally. Booking URLs are slower to resolve.
- OpenTable URLs resolve exactly — any valid
/r/{slug}profile URL, including older slugs, viaopentable_restaurant_url_to_id.
If you only have a URL, pass it. If you have the ID from a previous call, prefer it.
For Airbnb, send a positive decimal listing_id string, such as "22120898". Responses also use decimal strings so long IDs stay exact. Safe JSON integers up to 9007199254740991 are accepted, but larger numeric IDs are rejected to prevent precision loss.
Quota and billing
| Call type | Counts against your quota? |
|---|---|
initialize, tools/list, notifications |
No (free housekeeping) |
tools/call returning successful data or a definitive no-results outcome |
Yes — 1 quota unit per completed call |
| Invalid input, provider or operational failures, and protocol errors | No, including errors returned with HTTP 200 |
tools/call on an unknown tool name |
No |
Billing follows the tool outcome, not just the HTTP status. A completed lookup that confirms no results, such as no available rooms, still uses one quota unit.
Rate limits and monthly quotas are the same as the REST API — your tier dictates both. When you hit a quota limit, the tool returns a structured quota_exhausted error with retry_after and the reset time, so the agent can back off intelligently.
Per-tool usage appears in your activity dashboard as /mcp/tools/<tool_name> — so you can see which tools your agents call most.
Structured errors
When something goes wrong, tools return a structured dict your agent can read and react to, rather than a generic failure:
| Error | When | Shape |
|---|---|---|
invalid_input | Missing or bad parameter | {error, message, field} |
url_resolution_failed | Couldn't extract an ID from your URL | {error, message, provider, outcome} |
upstream_error | The upstream hotel provider returned an error | {error, message, provider} |
no_results | Query was valid but nothing matched | {error, message, provider} |
rate_limited | Service temporarily unavailable | {error, message, provider, retry_after} |
quota_exhausted | Monthly quota is used up | {error, message, retry_after, reset_at, remaining_quota} |
Auth failures (missing or invalid key) come back as JSON-RPC error code -32001 with HTTP 401 or 403.
What's NOT supported in v1
- JSON-RPC batches — send one tool call at a time. Batched requests return JSON-RPC
-32600. - Google Hotels max-price filter — the underlying provider doesn't expose one.
google_travel_searchsupports Google's discrete3.5,4.0, and4.5guest-rating thresholds; for price caps, filter the response client-side. - Pagination on
google_reviews_for_placebyquery—next_page_tokenonly works when you initially called withdata_id. The query path returns the first page plus the resolveddata_idso you can paginate from there.
Troubleshooting
Claude Desktop on Windows: mcp-remote won't start
The Windows failure ladder, in the order you'll hit it. All three are client-side — the server is fine.
| Symptom | Cause | Fix |
|---|---|---|
spawn npx ENOENT |
Claude Desktop can't find npx on its PATH. |
Use the absolute path to npx.cmd (see the Windows snippet above). Verify Node with where npx in Command Prompt — not the Node.js app / REPL. |
'npx' is not recognized |
Node isn't installed, or PATH was stripped by corporate policy. | Install Node LTS and restart the machine. If where npx is still blank in cmd but works elsewhere, use the absolute path. |
Unexpected content type: null |
A mcp-remote version regression. |
Pin mcp-remote@0.1.18. |
Check the server is reachable from your machine
Before blaming your client config, confirm the server answers from your network. Run this in Command Prompt (swap in your real key):
curl.exe -i -X POST https://api.stayapi.com/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "X-API-Key: YOUR_API_KEY" -d "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{},\"clientInfo\":{\"name\":\"curl\",\"version\":\"1.0\"}}}"
If it returns 200 and serverInfo: StayAPI, the server is fine and the problem is your client config.
PowerShell users: use curl.exe, not curl
curl is an alias for Invoke-WebRequest, which silently ignores -H/-d. Use curl.exe (as above) or run the command from Command Prompt.
401 Unauthorized on initialize
Your X-API-Key header is missing or wrong. Sign in and copy your current key from the dashboard and re-add the connector. Connected through a Claude connector? Disconnect and connect it again.
403 Forbidden
Your account is blocked or your IP isn't in your account's allowlist (Enterprise tier feature). Contact info@stayapi.com.
quota_exhausted on every tool call
You've used your monthly quota. The error includes reset_at — wait until then or upgrade your plan.
url_resolution_failed from Booking tools
The URL didn't resolve to a hotel. Use a canonical Booking URL (https://www.booking.com/hotel/{cc}/{slug}.html), not a search-results URL. Or pass hotel_id directly if you have it.
Tool calls take a long time
booking_hotel_details(url=…) and booking_hotel_reviews(url=…) resolve the URL via a real-browser fetch, which is slower. Pass hotel_id directly if you have it.
My agent isn't using the StayAPI tools
Some agents need the tool descriptions to clearly match the request. Try mentioning a hotel name, URL, or the platform explicitly: "use StayAPI to find…".
Privacy & security
- Your
X-API-Keyis sent on every request as a custom HTTP header. Treat it like any other API credential — don't paste it into shared chats or commit it to a repo. - Claude connectors and the ChatGPT app sign in with OAuth and never see your API key. Their access tokens expire after an hour and are refreshed automatically; revoke a connection any time under Connected apps on your MCP setup page.
- Tool inputs (URLs, hotel IDs, queries) are logged for analytics and debugging, same as REST API calls.
- All connections to
https://api.stayapi.com/mcpare TLS-encrypted. - The MCP server runs on the same infrastructure as the REST API. Same SLA, same data residency, same incident response.