smoobu-availability-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@smoobu-availability-mcpcheck availability at casa-caribe for Feb 10-17, 2 guests"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
smoobu-availability-mcp
A public, read-only Model Context Protocol server for vacation rentals in Puerto Viejo de Talamanca, Costa Rica. It answers questions about prices and availability using the Smoobu API. It speaks Streamable HTTP at /mcp and exposes four tools. Any AI agent or MCP client can use it without credentials. It never creates, changes or cancels a booking, and it never returns guest or reservation data.
Placeholder catalog. src/catalog.ts ships with two fictional properties, casa-caribe and jungle-studio, and CATALOG_IS_PLACEHOLDER = true. Their Smoobu apartment ids are made up. Replace the catalog with your real properties before going live. See the go-live checklist.
Tools
Tool | Inputs | Returns |
| none |
|
|
| For each property (or the one requested): either |
|
| One entry per day: |
|
| A booking-page |
Limits:
All dates are
YYYY-MM-DD, interpreted in theAmerica/Costa_Ricatime zone.Stays (
check_availability,get_booking_link): arrival is today or later, arrival is at most 18 months ahead, 1 to 60 nights.Guests: 1 up to the property's
maxGuests. Whenpropertyis omitted incheck_availability, the upper bound is the largestmaxGuestsin the catalog, and properties that sleep fewer guests are returned as unavailable with a reason.Calendar (
get_calendar):fromis today or later,tois inclusive and at most 18 months ahead, at most 92 days per call.
Prices exclude taxes and any cleaning or service fees. The final total is shown on the booking page.
Every tool is annotated readOnlyHint: true, openWorldHint: false, idempotentHint: true and destructiveHint: false.
Typical flow for an agent: list_properties -> check_availability -> get_booking_link. get_calendar is for browsing open dates and nightly prices.
Related MCP server: com.stayingapi/hotel-vacation-rental-mcp
Privacy guarantees
The Smoobu API key is account-wide. It can read reservations, guests and payments, not only rates. Privacy is therefore enforced by this code alone. The table lists each guarantee, where it is enforced and which test covers it.
Guarantee | Enforced in | Tested by |
Only three Smoobu calls are possible |
|
|
Upstream JSON is never forwarded |
|
|
Public identity comes only from the catalog |
|
|
Logs carry no data |
|
|
Endpoint allowlist
src/smoobu/allowlist.ts lists exactly three method and path pairs:
Method | Path | Used for |
GET |
|
|
GET |
|
|
POST |
|
|
SmoobuClient.request() in src/smoobu/client.ts is the only code path that calls fetch. Its first statement is assertAllowed(method, path), so a call that is not on the list throws before any network activity. The match is exact: method in upper case, bare path, no query string. There is no code for reservations, guests, messages or payments anywhere in the repository.
test/allowlist.test.ts checks that every other method and path is refused, including GET /api/reservations, and that fetch is called zero times when that happens. test/privacy.test.ts and test/e2e.test.ts also record every outgoing call and check it is on the list.
Upstream JSON is never forwarded
SmoobuClientparses each response and copies only the fields it needs into the minimal internal types insrc/smoobu/types.ts. The raw JSON is discarded inside the client.Each tool builds its output field by field from those internal types and the catalog.
Every successful output is validated against a zod
strictObjectschema insrc/schemas.tsbefore it is returned (ok()insrc/tools/shared.ts). Strict objects reject unknown keys.If a field ever leaked into an output, validation would throw and the caller would get a generic error instead of the field.
Public identity comes only from the catalog
Property slugs, names, bedrooms and guest limits come from the hand-written
src/catalog.ts. Nothing is read from Smoobu's apartment list at request time.Smoobu apartment ids, internal apartment names, channel names and the reason a day is closed (booked or blocked) never leave the server.
A calendar day is only
available: trueoravailable: false.When
check_availabilityrejects a stay, thereasonis composed from Smoobu's numeric rule code (400 to 404) and numbers such as the minimum stay. Smoobu's own message text is never used.
Logging
src/log.tsis the only logger. Its field type allows numbers and booleans only, so a log line can carry a status code or an attempt number but never a string.ESLint
no-consoleis an error insrc/, with exceptions only forsrc/log.ts, the local entry pointsrc/local.ts, the operator scriptsrc/scripts/andapi/mcp.ts, which logs the name of a missing environment variable at startup.Response bodies, URLs and API keys are never logged.
Upstream error messages (
SmoobuUpstreamErrorinsrc/errors.ts) carry only the HTTP status and attempt count, never the body.Configuration errors name the missing variable, never its value.
Poisoned test fixtures
The mock Smoobu in test/mockSmoobu.ts plants guest-like data from test/fixtures/poison.ts in every response: guest names, an email, a phone number, an address, reservation ids and references, a channel name, an internal apartment name, a block reason, a guest notice, the customer id and a string that looks like an API key. Unavailable days in the rates response carry a fake reservation. Booked apartments in the availability response carry a Smoobu message that names the guest.
test/privacy.test.ts calls every tool across many valid and invalid inputs and checks every output, including error outputs. It asserts that:
no fixture value appears;
no email or phone pattern appears;
no Smoobu apartment id appears;
no key outside the output schema appears.
Robustness for anonymous traffic
Measure | Details | Code |
Input validation | Friendly error text, |
|
Rate limit | Per-client token bucket (IPv4 address or IPv6 /64), in memory, per instance. Returns HTTP 429 with |
|
Upstream budget | A second token bucket in front of every Smoobu call (default 50 burst, 300 per minute, per instance) so no mix of callers can exhaust the account-wide Smoobu quota. After a Smoobu 429 with |
|
Caching | Rates for 300 s, availability for 120 s, in memory, per instance. Concurrent identical requests share one upstream call (single flight) |
|
Retry | Exponential backoff on 429, 5xx, network errors and timeouts. Honours |
|
Body limit | 64 KB per request |
|
CORS | Open ( |
|
Run locally
Prerequisites: Node.js 24 or later, and a Smoobu account with API access.
npm install
cp .env.example .envFill in .env:
Variable | Required | Meaning |
| yes | Smoobu API key. Create it in Smoobu under Settings > Advanced > API Keys. |
| recommended | The secret paired with the key. When set, requests are signed with HMAC. When empty, the client falls back to the legacy |
| yes | Your Smoobu customer (user) id. Required by |
| yes | Absolute http(s) URL of your public booking page. Placeholders: |
| no | Local server port. Default |
| no | Per-IP sustained rate. Default |
| no | Per-IP bucket size. Default |
| no | Budget for outgoing Smoobu calls, all callers together. Default |
| no | Bucket size for that budget. Default |
| no | Calendar cache lifetime. Default |
| no | Availability cache lifetime. Default |
src/config.ts also reads SMOOBU_BASE_URL, which overrides https://login.smoobu.com. It exists for testing, must be a bare https origin, and should be left unset in production.
Start the dev server:
npm run devIt listens on http://127.0.0.1:3000/mcp.
A plain
GET /mcpreturns a small JSON description of the server (name, version, tool names,readOnly: true). AGETasking fortext/event-streamgets 405: the server is stateless and has nothing to stream.GET /healthzreturnsok. This route exists only in the local server.
Check that every catalog entry points at a real apartment in your account:
npm run check-catalogIt prints OK or MISSING for each slug and its apartment id, and exits with code 1 if any id is missing. It warns if the catalog is still the placeholder.
Testing
npm test # vitest
npm run lint # eslint, type-checked rules
npm run typecheck # tsc including tests
npm run build # tsc to dist/No real Smoobu key is needed. Tests inject a mock fetch (test/mockSmoobu.ts) that:
serves the three allowlisted endpoints with deterministic prices, booked dates, minimum stays and no-arrival weekdays;
requires either the legacy or the HMAC auth headers, and returns 401 otherwise;
can fail the next N responses with a chosen status and headers, or return a malformed body, to exercise retries and error handling;
answers any other path with a pile of guest data, so an accidental call would be loud;
plants guest-like values in every response (see Poisoned test fixtures).
test/e2e.test.ts starts the real HTTP server on a random port and talks to it with the MCP SDK client over Streamable HTTP.
Test with MCP Inspector
With npm run dev running:
npx @modelcontextprotocol/inspectorIn the Inspector UI, set Transport Type to "Streamable HTTP".
Set URL to
http://127.0.0.1:3000/mcp.Click Connect.
Open Tools and click List Tools. You should see the four tools.
Call
check_availabilitywith, for example:
{ "arrival": "2027-02-10", "departure": "2027-02-15", "guests": 2 }While the catalog is still the placeholder, check_availability and get_calendar will not find the fictional apartment ids in your account. list_properties and get_booking_link do not call Smoobu and work regardless.
Raw JSON-RPC with curl:
curl -s http://127.0.0.1:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Connect from an MCP client
Most clients that support remote servers accept a config like this:
{
"mcpServers": {
"puerto-viejo-rentals": {
"type": "streamable-http",
"url": "https://<your-deployment>/mcp"
}
}
}The exact key names vary by client. No authentication is required, by design: the server is public and read-only.
Deploy to Vercel
The project deploys as a Vercel Function on the Node.js runtime.
vercel.jsonrewrites/mcpto/api/mcpand allows the function 60 s (worst case with retries is about 40 s)..vercelignoreuploads onlyapi/,src/,public/and the package files. In particular it keeps a local.envout of the deployment: Vercel's built-in ignore list covers.env.localbut not.env.public/robots.txtdisallows crawling and makespublic/the static root, so project files are never served as static assets.api/mcp.tsexports Web-standardGET,POST,DELETEandOPTIONShandlers. They share the same handler as the local server (src/http.ts).If configuration is missing, the function answers 503
{"error":"Server is not configured"}and logs the name of the missing variable.
Environment variables to set in the Vercel project:
Variable | Sensitive |
| yes |
| yes |
| yes |
| no |
| no, optional |
Mark the SMOOBU_* variables as Sensitive in the Vercel dashboard (Project > Settings > Environment Variables).
Commands:
npm i -g vercel
vercel login
vercel link
vercel env add SMOOBU_API_KEY production
vercel env add SMOOBU_API_SECRET production
vercel env add SMOOBU_CUSTOMER_ID production
vercel env add BOOKING_URL_TEMPLATE production
vercel --prodEach vercel env add prompts for the value. Add the optional tuning variables the same way if you need them.
Verify:
curl -s https://<project>.vercel.app/mcp
curl -s https://<project>.vercel.app/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The first returns the JSON description. The second returns the four tools. Also check that https://<project>.vercel.app/.env and /package.json return 404.
The cache and the rate limiter live in memory, so each function instance has its own. That is fine for a small public read-only service, but it is not a global quota. If you need one, implement the Cache interface (src/cache.ts) and the RateLimiter interface (src/rateLimit.ts) on a shared store such as Upstash Redis, or use Vercel WAF rate limiting in front of the function.
Go-live checklist
Replace the entries in
src/catalog.tswith your real properties: slug, public name, bedrooms, max guests, currency and Smoobu apartment id.Set
CATALOG_IS_PLACEHOLDER = falseinsrc/catalog.ts.With your real credentials in
.env, runnpm run check-catalog. Every line must sayOK.Run
npm test,npm run lintandnpm run typecheck.Set the environment variables in Vercel (see Deploy to Vercel).
Deploy with
vercel --prod.Deploy a preview first (
vercelwithout--prod) and confirmcheck_availabilityworks withSMOOBU_API_SECRETset. The Smoobu docs show only the legacy header for/booking/checkApartmentAvailability; if HMAC is rejected there, leave the secret empty for now and raise it with Smoobu before the 2026-10-31 sunset.Open MCP Inspector, connect with Streamable HTTP to
https://<project>.vercel.app/mcp, list the tools and call each one. Check thatcheck_availabilityreturns real prices for dates you know are open, and a reason for dates you know are booked.If calendar changes must show up faster, lower
CACHE_TTL_RATES_SECONDSandCACHE_TTL_AVAILABILITY_SECONDS.
Project layout
.
├── api/
│ └── mcp.ts Vercel function: GET/POST/DELETE/OPTIONS handlers
├── src/
│ ├── app.ts Composition root: config, Smoobu client, cache, rate limiter
│ ├── booking.ts Booking URL template validation and filling
│ ├── cache.ts Cache interface, in-memory cache, single-flight loader
│ ├── catalog.ts Hand-written public catalog (placeholder)
│ ├── config.ts Environment variable parsing
│ ├── errors.ts User-facing and upstream error types
│ ├── http.ts Stateless Streamable HTTP handler, CORS, rate limit, batch rejection
│ ├── local.ts Local entry point for npm run dev
│ ├── log.ts Structured logger (numbers and booleans only)
│ ├── nodeServer.ts Node http to Web Request/Response adapter, /healthz
│ ├── rateLimit.ts Rate limiter interface and per-IP token bucket
│ ├── schemas.ts Strict zod output schemas for every tool
│ ├── server.ts Builds the McpServer and registers the four tools
│ ├── validation.ts Date, stay and calendar range rules
│ ├── scripts/
│ │ └── check-catalog.ts Verifies catalog apartment ids against the account
│ ├── smoobu/
│ │ ├── allowlist.ts The three permitted Smoobu calls
│ │ ├── auth.ts HMAC and legacy auth headers
│ │ ├── client.ts Smoobu client: allowlist, retry, response reduction
│ │ └── types.ts Minimal internal views of Smoobu data
│ └── tools/
│ ├── checkAvailability.ts
│ ├── getBookingLink.ts
│ ├── getCalendar.ts
│ ├── listProperties.ts
│ └── shared.ts Annotations, output validation, error handling
├── test/
│ ├── fixtures/poison.ts Guest-like values planted in mock responses
│ ├── mockSmoobu.ts Mock Smoobu fetch
│ ├── helpers.ts Test app with fixed clock and test catalog
│ ├── e2e.test.ts Full HTTP round trip with the MCP SDK client
│ ├── allowlist.test.ts Non-allowlisted calls are refused before fetch
│ ├── privacy.test.ts No fixture data in any tool output
│ └── *.test.ts Unit tests: auth, booking, cache, catalog, client, config, http, rateLimit, tools, validation
│ ├── deploy.test.ts .vercelignore, vercel.json and .env.example guards
├── public/robots.txt Static root; disallows crawlers
├── .env.example
├── .vercelignore
├── vercel.json
├── DECISIONS.md Defaults chosen and why
└── LICENSELicense
GPL-3.0. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only property facts, indicative availability, authorised booking links and guest-safe support.
Hosts: read and record bookings, change prices, draft listings, rename devices. No payments.
AI-native Caribbean vacation-rental registry: pricing, licensing, availability, source-linked.
Read-only MCP server: let AI agents read your ORANO saved-video library, tasks, and memory.
Related MCP Servers
- AlicenseBqualityBmaintenanceA read-only hospitality-focused MCP server that enables users to retrieve reservation details, listing briefs, and guest conversation contexts from Hostaway. It simplifies hospitality workflows by providing specialized tools for searching threads and viewing reservation data through natural language interfaces.652 npmMIT
- AlicenseNot gradedqualityBmaintenanceMCP server for searching stays, comparing prices across Booking.com, Airbnb, Vrbo, and Google Hotels, and fetching reviews via natural language in AI assistants.MIT
- FlicenseAqualityBmaintenanceA read-only MCP server that lets AI assistants query the WebHotelier REST API for live hotel data, including property info, room availability, rates, calendars, and offers via natural language tools.8-
- FlicenseNot gradedqualityBmaintenanceMCP server for querying MyDataValue's Booking.com and Airbnb property data, including pricing, promotions, reviews, and performance metrics, via a read-only connector with automatic OAuth token rotation.-