Search CuddlyNest hotels and read rooms, prices, availability and cancellation policies
A Model Context Protocol (MCP) server for searching CuddlyNest hotels and retrieving listing details, including room options, prices, availability and cancellation policies.
Read-only by design: search and listing details only. No booking, no payment.
This server reads that data the same way a visitor does: it opens the real, public listing page in a headless browser (Playwright/Chromium), lets the page's own JavaScript load the rooms, waits for them to render, and reads the result out of the DOM.
| Data | Source |
|---|---|
| Name, description, address, coordinates, star rating, amenities, images | Listing page schema.org ld+json + Open Graph tags (cuddlynest.ts) |
Room title, partner, unit_price, remaining_rooms, price_breakdown, cancellation_policy (incl. .text), room_filters | Rendered listing page DOM, via a React-fiber walk (scrape-listing.ts) |
| Destination → place candidates + top hotels | autosuggestion-2-0.cuddlynest.com (public, no auth) |
| Destination → broader city hotel list (~60–250) | discovery-pages.cuddlynest.com/fetch_geopage/<ct-id> (public); <ct-id> recovered from a hotel's product-detail breadcrumbs |
product_id → name / city / breadcrumbs | ldp-2-0-product-details.cuddlynest.com/api/v1/productDetail (public) |
product_id → canonical listing path | /hotel/-<id> server redirect |
extractRoomsFromDom() walks every price-shaped text node (COL$742,637), then
walks up its React fiber tree to the nearest ancestor component whose props
carry both unit_price and roomGroups. Those props are the room offer the
page already rendered.
This is coupled to CuddlyNest's current frontend internals (a React prop shape,
not a stable contract). If they ship a frontend change it can start returning
zero rooms even though the public page still shows prices. The single place to
update is the detector condition 'unit_price' in p && 'roomGroups' in p in
scrape-listing.ts. npm run e2e:sansiraka is meant to
catch that early (non-zero exit, not a silent empty result).
The fromPriceText ("From COL$…") field uses a looser heuristic and can come
back null even on a healthy scrape; rooms.fromPrice (cheapest extracted
unit) is the reliable figure.
npm install runs playwright install chromium automatically (postinstall); if that is blocked in your
environment, run npx playwright install chromium once by hand.Published as cuddlynest-mcp on
npm and listed in the official MCP registry
as io.github.mafedelahoz/cuddlynest-mcp.
{
"mcpServers": {
"cuddlynest": {
"command": "npx",
"args": ["-y", "cuddlynest-mcp"]
}
}
}
Add "--ignore-robots-txt" to args to bypass robots.txt for the
listing-page fetches. CUDDLYNEST_SCRAPE_TIMEOUT_MS (default 35000) caps how
long the browser waits for prices to render.
An MCPB bundle (.mcpb) for Claude Desktop is attached to each
GitHub release — note it
does not bundle Chromium, so run npx playwright install chromium once after
installing it that way.
Default transport is stdio. For a hosted deployment, run it over Streamable HTTP:
node dist/index.js --http 8080 # or: MCP_TRANSPORT=http PORT=8080 node dist/index.js
# POST http://<host>:8080/mcp — JSON-RPC (stateless, no sessions)
# GET http://<host>:8080/health — liveness
Both tools are annotated readOnlyHint: true — they never write, book, or pay.
cuddlynest_searchSearch a destination and the top hotels there, from public CuddlyNest APIs
(autosuggestion-2-0 for the fuzzy match, discovery-pages geo pages for the
broader city list). Prices are not here — pass a hotel's productId to
cuddlynest_listing_details.
| Parameter | Required | Description |
|---|---|---|
destination | yes | City / area string, e.g. "Cartagena, Colombia" |
hotelsOnly | no | Omit the places[] block (default false) |
fullCityList | no | Also pull the geo-page city list (~60–250 hotels) when it can be resolved and verified against the destination — a few extra requests (default true) |
checkin, checkout, adults, children, childAges, infants, rooms, currency | no | echoed back for downstream use |
Returns: { query, guests, places[], city, hotelSource, hotelCount, hotels[], note }.
Each hotels[] entry: productId, name, url, slug, propertyType,
starRating, guestRating (/10) + guestRatingText, reviewCount, and — from
the geo page — images[], distanceFromCenterKm, featuredAmenities[].
hotelSource is "autosuggest" or "autosuggest+geopage". The list is
top-matches scale, not full inventory — CuddlyNest's real results page
(/sr/…) is bot-blocked and Disallowed in robots.txt.
cuddlynest_listing_detailsStatic basics and rooms/pricing for one hotel.
| Parameter | Required | Description |
|---|---|---|
hotel | yes | Listing URL or numeric product_id (trailing number in the URL) |
checkin, checkout | for pricing | YYYY-MM-DD — required to read rooms/prices |
adults, children, childAges, infants, rooms | no | defaults 2 / 0 / – / 0 / 1 |
currency | no | ISO 4217, default USD |
ignoreRobotsText | no | ignore robots.txt for the static fetch |
Returns: { productId, hotelUrl, guests, staticListing, staticError, rooms, roomsError, notes }.
rooms.units[] is the extracted room offers, each with title, partnerName,
unitPrice, currency, remainingRooms, guests, cancellationPolicyType,
cancellationPolicyText, priceBreakdown, roomFilters. rooms also carries
fromPrice, partnersSeen, listingUrl, scrapedAt.
npm install # installs deps + Chromium (postinstall)
npm run build # sync-version + tsc -> dist/
npm run typecheck
npm test # offline: smoke test (stdio) + scraper tests
npm run e2e:sansiraka # ONLINE: real scrape of cuddlynest.com, structural asserts
npm run watch
index.ts — MCP server, tool schemas, stdio and Streamable HTTP transports,
robots.txt handlingcuddlynest.ts — hotel-URL parsing, static-listing ld+json parse,
destination autosuggestion, result shapingscrape-listing.ts — resolveListingPath, buildListingUrl, scrapeListing
(headless browser), extractRoomsFromDom (React-fiber walk)util.ts — generic object/JSON helpersMIT — see LICENSE.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y cuddlynest-mcpMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-mafedelahoz-cuddlynest-mcp": {
"command": "npx",
"args": [
"-y",
"cuddlynest-mcp"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.mafedelahoz/cuddlynest-mcp works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.