Multi-tenant hosted MCP server that exposes the Hospitable Public API v2 as MCP tools. Runs at https://hospitable.nlma.io. Each user authenticates with their own Hospitable Personal Access Token (PAT).
| Tool | Endpoint |
|---|---|
get_user |
GET /user |
list_properties |
GET /properties (supports all: true to fetch every page) |
get_property |
GET /properties/{uuid} |
search_properties |
GET /properties/search (Direct site required) |
get_calendar |
GET /properties/{uuid}/calendar |
update_calendar |
PUT /properties/{uuid}/calendar (max 60 dates/call). Smart Rates properties return 422 with actionable guidance — see get_smart_rates_config and update_property instead. |
list_reservations |
GET /reservations (requires properties[]; supports all: true) |
get_reservation |
GET /reservations/{uuid} (supports include=financials,financialsV2,guest,properties,listings,review) |
list_messages |
GET /reservations/{uuid}/messages |
send_message |
POST /reservations/{uuid}/messages |
list_property_reviews |
GET /properties/{uuid}/reviews (supports all: true) |
respond_to_review |
POST /reviews/{uuid}/respond |
list_transactions |
GET /transactions (undocumented) |
get_transaction |
GET /transactions/{uuid} |
list_payouts |
GET /payouts (undocumented — cleaner shape) |
get_payout |
GET /payouts/{uuid} |
list_listings |
flatten include=listings from properties |
revenue_summary |
client-side aggregator over reservations.financialsV2 |
hospitable_request |
arbitrary call — escape hatch |
Webhook-subscription endpoints are not exposed: the Hospitable v2 Public API does not provide them. Reservation financials are available via the include=financials / include=financialsV2 parameter on list_reservations / get_reservation. Webhook delivery is configured out-of-band through Hospitable support.
Hospitable's private web-app API at https://api.hospitable.com/v1 exposes owner-management accounting data that the documented Public API v2 doesn't: owner statements, reservation commissions, commission agreements, owners, profit-and-loss reports. These are only enabled on this server when the operator configures a JWT (see .env.example). They operate on the JWT owner's Hospitable account, not per-tenant, so they should be gated to one tenant via HOSPITABLE_INTERNAL_OPERATOR_TENANT_HASH on multi-user deployments.
| Tool | Endpoint / purpose |
|---|---|
internal_whoami |
Decoded JWT payload + live /v1/user/permissions — shows days until JWT expiry |
list_statements |
POST /v1/operations/v2/statements?status={draft|in_review|published|paid} |
get_statement |
GET /v1/operations/v2/statements/{id} — full line items + summary |
statement_commission_summary |
Synthesis: walks every statement, filters to a month, sums Reservation commissions to manager per property. Direct answer to "what did manager commissions total for ". |
list_commission_agreements |
GET /v1/commissions |
get_commission_agreement |
GET /v1/commissions/{id} |
get_commission_agreement_properties |
GET /v1/commissions/{id}/properties |
list_owners |
GET /v1/owners |
get_owner |
GET /v1/owners/{id} |
update_property |
Safe PUT /v1/properties/{uuid} — does GET-then-PUT and replays critical array fields (tags, amenities, room_details, photos, listings, ical_imports, languages, types) so a partial update never silently wipes them. Always use this instead of hospitable_internal_request PUT /properties/{uuid} for property metadata edits. |
get_smart_rates_config |
GET /v1/properties/{uuid}/pricing — read Smart Rates dynamic-pricing config (dynamic_pricing_enabled, base_prices, last_minute_sensitivity curve, gap_night_rules, etc.) |
run_profit_and_loss |
POST /v1/profit-and-loss |
list_businesses |
GET /v1/businesses |
list_teams |
GET /v1/teams/ |
hospitable_internal_request |
arbitrary call — escape hatch for the internal API |
JWT capture & rotation: Log into https://my.hospitable.com in a browser, open DevTools → Application → Local Storage → my.hospitable.com, copy the accessToken value, paste into the server's HOSPITABLE_INTERNAL_JWT env var, restart. The JWT is valid for ~28 days. internal_whoami reports days_until_expiry so you can schedule refreshes.
These endpoints are undocumented and may drift without notice. They also have no public rate-limit guarantees; treat them as best-effort.
Two flows are supported on the same /mcp endpoint:
- Raw PAT bearer (Claude Code)
claude mcp add hospitable -t http https://hospitable.nlma.io/mcp \ --header "Authorization: Bearer <your-hospitable-PAT>" - MCP OAuth wrap (claude.ai) — point claude.ai at
https://hospitable.nlma.io/mcp. You'll be redirected to a login page, paste your PAT, and claude.ai receives a server-minted bearer token that maps back to your PAT in memory. Access tokens live 1 hour; refresh tokens live 30 days.
Generate a PAT at my.hospitable.com/settings/api. Some tools (send_message, respond_to_review) need extra scopes (message:write, reviews:write) which you must request from team-platform@hospitable.com.
Only sha256(pat + API_KEY_HASH_SALT) is persisted (as the tenant identifier). PATs themselves are held only in process memory. Every successful or failed tool call is recorded in audit_log keyed by that tenant hash.
npm install
cp .env.example .env # set DATABASE_URL and API_KEY_HASH_SALT
npm run build
npm start # listens on :3004
curl http://localhost:3004/healthPoint Claude Code at the local server:
claude mcp add hospitable-local -t http http://localhost:3004/mcp \
--header "Authorization: Bearer <your-hospitable-PAT>"Required env:
| Var | Notes |
|---|---|
DATABASE_URL |
Postgres connection string |
API_KEY_HASH_SALT |
Random 32+ char string; used when hashing PATs for tenant IDs |
BASE_URL |
Public URL, e.g. https://hospitable.nlma.io (defaults to http://localhost:$PORT) |
PORT |
default 3004 |
cp .env.example .env # set DATABASE_URL/PGHOST and API_KEY_HASH_SALT
docker compose up -d --build
curl http://localhost:3004/healthThe container listens on HOST:PORT (default 0.0.0.0:3004) and runs on Docker's default bridge network with the port published — no network_mode: host needed. Point DATABASE_URL (or the discrete PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE vars) at a Postgres instance reachable from inside the container: localhost in DATABASE_URL resolves to the container itself on a bridge network, not the Docker host, so use an external/managed Postgres or the host's LAN/gateway address.
The server runs as a plain Node process under systemd on the Hostinger VPS at 178.16.141.166, alongside the other *.nlma.io MCP servers. No Docker.
Push an update:
bash scripts/push-to-vps.shThis tars the working tree, ships it to root@178.16.141.166:/opt/hospitable-mcp/, runs npm ci && npm run build, restarts the systemd unit, and hits /health.
Server layout:
- Source:
/opt/hospitable-mcp/ - Env:
/opt/hospitable-mcp/.env(chmod 600 — containsDATABASE_URL,API_KEY_HASH_SALT) - systemd unit:
/etc/systemd/system/hospitable-mcp.service - nginx site:
/etc/nginx/sites-available/hospitable.nlma.io - TLS:
/etc/letsencrypt/live/hospitable.nlma.io/(certbot auto-renew) - Listens on
127.0.0.1:3004(nginx passthrough from:443) - Postgres: local instance, DB
hospitable_mcp, rolehospitable_mcp
Tail logs:
ssh root@178.16.141.166 'journalctl -u hospitable-mcp -f'Rotate a tenant's PAT:
Tenants are identified by sha256(pat + API_KEY_HASH_SALT) — when a user rotates their PAT on Hospitable's side, the next call with the new token creates a new tenants row automatically. No server-side action needed. To reclaim disk from stale rows, delete directly:
ssh root@178.16.141.166 \
"sudo -u postgres psql hospitable_mcp -c 'DELETE FROM tenants WHERE last_active_at < NOW() - INTERVAL ''90 days'';'"Restart:
ssh root@178.16.141.166 'systemctl restart hospitable-mcp'- src/http.ts — Express app, Streamable HTTP MCP transport, OAuth routes.
- src/auth.ts — bearer middleware; accepts raw PATs or OAuth opaque tokens and resolves to a
TenantContext. - src/oauth.ts — HTML login form + MCP OAuth provider (in-memory token store).
- src/hospitable/client.ts — fetch wrapper with per-minute throttle, 429/503 retry, 30s timeout, and per-endpoint methods.
paginate()helper walks all pages for list endpoints. - src/tools/register.ts — registers every MCP tool and validates inputs with Zod.
- src/tools/_shared.ts —
runTool()wraps every handler with error formatting and writes toaudit_log. - src/server.ts —
createMcpServer(ctx)— instantiated per MCP session soctx.patis closed over and never shared between tenants.
Copyright © 2026 Next Level Management Advisors, LLC.
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE. If you run a modified version over a network, the AGPL requires you to make your modified source available to its users.
Commercial licensing: to use this in a closed-source or commercial product, or to host a modified version without publishing your source, a commercial license is available — contact forrest@nlma.io.