night-margin-mcp

night-margin-mcp

An MCP server that shows every pricing decision for a short-term rental portfolio net of turnover costs, focusing on net per available night to protect profit margins.

Category
访问服务器

README

night-margin-mcp — Margin Engine

Wheelhouse optimizes what guests pay. night-margin protects what you keep.

Built by a property manager who runs 73 listings in Lapland — for operators like us.

An MCP server that shows every pricing decision for a short-term rental portfolio net of turnover costs — the margin guard for Wheelhouse pricing, the profit layer underneath its revenue optimization (ADR, RevPAR). The key metric: net per available night.

Quick start

claude mcp add margin -- npx -y night-margin-mcp

That's it — works with zero config on synthetic demo data. Open Claude and ask:

Where is my portfolio leaking money in June 2026?

You'll get the leak figure, the net-per-night metric, and your best and worst properties in under a minute. When you're ready to run the analysis against your own Wheelhouse portfolio, see Wheelhouse API key below.

On live data the first call is slower: one request per listing, serial, to stay inside Wheelhouse's 60 req/min limit — 3 listings ≈ 5 s, 73 listings ≈ 90 s. Results are then cached for 10 minutes.

What you get

The first line is the pain, in euros — this is actual zero-config output (June 2026, demo data):

€132 is leaking from 8 bookings that don't cover their own turnover cost. (4.0% of booked nights are net-negative) Net per available night: €74.7 Occupancy 72.9% (175 booked, 65 gap nights) Gross €22,821 − turnover costs €4,900 = net €17,921

Property Net/night Booked nights Gap nights Net €
demo-1br-03 €21.4 15 15 €643
demo-1br-01 €32 18 12 €960
demo-3br-08 €178.1 30 0 €5,344

Same math, your numbers: set WHEELHOUSE_API_KEY and the analysis runs on your live portfolio.

Live demo

Want to see the output before installing anything? https://mikki321.github.io/night-margin-mcp/ — portfolio KPIs, a per-property heatmap of net per available night, and a triage table of net-negative bookings, generated by this server from the synthetic demo portfolio that ships with the package.

Example prompts

The server provides ten tools: three read-only analysis tools, a four-tool decision loop (see The decision loop), a proactive alerts tool (see Proactive alerts), review_history — a read-only mirror of your own past months net of estimated turnover cost (a description of what happened, never a forecast) — and margin_copilot, which ranks the below-floor gap nights into money-moves, weighs the options for each and marks the recommended one (read-only; staging routes through propose_decisions → apply_decision). No dates needed — analyze_portfolio and compare_strategies default to the last 30 + next 90 days (recent turnovers + your booking horizon), propose_decisions to the next 30 days; pass from/to to change. Try these directly in Claude:

  1. analyze_portfolio — where is the money leaking?

    Where is my portfolio leaking money?

  2. compare_strategies — gross vs. net in the same picture:

    Compare two strategies for June 2026: (A) fill gap nights at a 40% discount and (B) raise the minimum stay to three nights with a 10% price increase. Which one delivers the better net per night?

  3. gap_night_check — is a single gap night worth filling?

    Property demo-1br-01 has a gap night on 2026-06-23. What's the floor price, and is the night worth filling?

  4. propose_decisions — which upcoming nights are at risk of selling below cost?

    Which gap nights are about to sell below my cost floor?

  5. apply_decision — act on a proposal:

    Apply decision d3

    Without explicit confirmation this is a dry run: it prints the exact rate payload that would be written and changes nothing. Prices change only when you confirm the write, and every applied decision can be undone with revert_decision.

  6. Risk presets — propose_decisions and gap_night_check both take an optional risk parameter (conservative | recommended | aggressive, default recommended) that scales the floor's minimum margin — Wheelhouse's own CON/REC/AGG language:

    What should I do this week? Use the aggressive risk preset.

  7. check_alerts — proactive check for gap-floor breaches and new bookings (see Proactive alerts):

    Check for any pricing alerts.

The core message of the demo: filling gaps at a discount can grow revenue while shrinking profit — a 2-night and a 7-night booking consume the same cleaning.

The decision loop

New in 0.3.0 — analysis turns into safe, reversible action:

  1. propose_decisions finds upcoming gap nights (next 30 days by default) where the current price recommendation sits below your cost floor (turnover + travel + MIN_MARGIN, scaled by an optional risk preset — see Example prompts) and proposes fixing those nights at the floor so they can't sell below cost. Proposals go into a local decision log — proposing never touches prices.
  2. apply_decision writes one proposal to Wheelhouse as fixed custom rates — but only with explicit confirm: true. The default is a dry run showing the exact payload without writing anything. Before the first real write, the prior custom rates are snapshotted to the decision log, so revert_decision can always delete what was written and restore what was there before.
  3. set_target stores a monthly gross revenue target per property; analyze_portfolio then reports progress toward any targets whose month overlaps the analysis window.

The safety model in one line: the analysis tools (analyze_portfolio, compare_strategies, gap_night_check) and propose_decisions never change any prices; writes happen only through apply_decision/revert_decision with an explicit confirm: true, always target the listing's own channel, and every write is revertible. Without a WHEELHOUSE_API_KEY you can run the whole loop up to the dry run on demo data — real writes require the key and a fresh propose against your own portfolio. The decision log and targets live locally in NM_STATE_DIR (default ~/.night-margin).

Proactive alerts (watch mode)

check_alerts is read-only and checks two things: (1) upcoming gap nights priced below your cost floor (same data as propose_decisions, always the recommended risk preset, but this tool never saves proposals to the decision log), and (2) new bookings since the last check, each shown net of turnover costs — the same twist as the rest of the server, now in a booking notification. The first run establishes a baseline (records existing bookings without alerting on them); later runs alert on bookings that were not present in the previous check. The Wheelhouse reservation payload carries no creation timestamp, so a booking made earlier will surface the first time its dates enter the 120-day horizon — "new to this check", not "booked since yesterday".

By default check_alerts is silent — it just returns the report as text, same as every other tool. To get pushed notifications, configure a channel:

Variable Description
NM_TELEGRAM_BOT_TOKEN / NM_TELEGRAM_CHAT_ID Send alerts via a Telegram bot (checked first).
NM_WEBHOOK_URL Send alerts to a generic incoming webhook (Slack-compatible {text} JSON body) — used only if Telegram isn't configured.

Neither is required — without either, check_alerts still works and simply says no channel is configured. Notifications are off unless you set one of these — nothing is ever sent by default, and the token/URL are never logged or echoed back in error messages.

For unattended checks (e.g. a daily cron job), run the server with --watch: it runs check_alerts once (sending a notification if a channel is configured), prints the report to stdout, and exits — no MCP server, no stdio transport:

0 8 * * * WHEELHOUSE_API_KEY=... NM_TELEGRAM_BOT_TOKEN=... NM_TELEGRAM_CHAT_ID=... npx -y night-margin-mcp --watch

Configuration (env)

Every setting is optional — without them, the server runs the manual cost model on synthetic demo data. Environment variables are passed via claude mcp add's -e flags:

claude mcp add margin -e WHEELHOUSE_API_KEY=xxx -e COST_SOURCE=csv -e CSV_PATH=/path/to/costs.csv -- npx -y night-margin-mcp
Variable Default Description
WHEELHOUSE_API_KEY – Your own Wheelhouse integration key. When set, bookings are fetched from the Wheelhouse RM API; without it, synthetic demo data is used.
WHEELHOUSE_CHANNEL – Optional override for the channel parameter on Wheelhouse read calls (reservations, price recommendations). By default each listing's own channel field is used, which the API accepts. Writes (apply_decision) always go to the listing's own channel — this override never affects writes.
COST_SOURCE manual Source of turnover costs: manual | csv | cleanhub.
AVG_TURNOVER_COST 70 Manual mode: € per turnover (cleaning). Also serves as the fallback in CSV mode when a booking has no cost row.
COST_TIERS – Optional tiering, e.g. 1br:55,2br:70,3br:95 — matched against the property's property_id by substring; everything else gets AVG_TURNOVER_COST.
CSV_PATH – CSV mode: path to the cost CSV (required when COST_SOURCE=csv). See CSV mode.
CLEANHUB_API_URL / CLEANHUB_TOKEN – CleanHub mode: actual turnover costs from the CleanHub field-operations platform over HTTP (both required when COST_SOURCE=cleanhub).
MIN_MARGIN 25 Minimum margin € for the gap-night floor: floor = turnover + travel + MIN_MARGIN (scaled by the risk preset — see Example prompts). Used in gap_night_check's fill/skip verdict and in propose_decisions'/check_alerts' floor proposals.
NM_STATE_DIR ~/.night-margin Local state directory for the decision log, monthly targets, and the seen-bookings set (used by propose_decisions, apply_decision, revert_decision, set_target, check_alerts). Nothing is ever stored in the package directory.
NM_TELEGRAM_BOT_TOKEN / NM_TELEGRAM_CHAT_ID – Optional: send check_alerts notifications via a Telegram bot. See Proactive alerts.
NM_WEBHOOK_URL – Optional: send check_alerts notifications to a Slack-compatible incoming webhook (used only if Telegram isn't configured). See Proactive alerts.

CSV mode

If you know your actual turnover costs per booking, a CSV gives you the most accurate analysis. Schema (header row required):

reservation_id,property_id,checkin,checkout,nights,gross_revenue,cleaning_cost,travel_cost,laundry_cost,turnover_date,is_sunday_or_holiday
demo-1br-01-r1,demo-1br-01,2026-06-03,2026-06-10,7,571,56,16,9,2026-06-10,false

A synthetic sample file (887 rows, covering the full 2026 calendar year of the demo data) ships with the package at examples/sample-costs.csv, and can be downloaded directly:

https://raw.githubusercontent.com/mikki321/night-margin-mcp/main/examples/sample-costs.csv

Usage:

curl -o costs.csv "https://raw.githubusercontent.com/mikki321/night-margin-mcp/main/examples/sample-costs.csv"
claude mcp add margin -e COST_SOURCE=csv -e CSV_PATH="$PWD/costs.csv" -- npx -y night-margin-mcp

Cost rows are matched to bookings primarily by reservation_id, and secondarily by the composite key property_id|checkin|checkout. The tool response reports the attribution quality — e.g. "Cost attribution: 289/289 bookings matched by reservation_id", or with mixed sources "Cost attribution: 41 by reservation_id, 6 by composite key, 3 by average fallback (50 total)". If a row is missing and no AVG_TURNOVER_COST fallback is set, you get a clear error listing the affected bookings.

Wheelhouse API key

Without a key, the server runs on synthetic demo data (and says so in every response). With a key, you get your real bookings from the Wheelhouse RM API: the server fetches your listings and reservations, and every metric is computed from your own portfolio.

  • The key is your own Wheelhouse integration key (the API's X-Integration-Api-Key header). Get it from your Wheelhouse account's integration settings or from Wheelhouse support — API documentation: https://api.usewheelhouse.com/wheelhouse_rm_api.
  • Pass it as an environment variable: claude mcp add margin -e WHEELHOUSE_API_KEY=xxx -- npx -y night-margin-mcp.
  • Never commit the key to git — not in a .env file in the repo, not in README examples, not in commits. The key travels only as an environment variable.

If the key is invalid or the network flakes, the server returns a clear error with instructions — it does not silently fall back to demo data, so you'll never mistake mock numbers for your own.

For developers

Local development:

npm install
npm test
npm run build
claude mcp add margin -- node "<repo path>/dist/index.js"

If the npm package doesn't work for any reason, the same server installs directly from GitHub:

claude mcp add margin -- npx -y github:mikki321/night-margin-mcp

Exploring the Wheelhouse API with curl

Use these commands to verify auth and inspect response shapes (redact guest names/emails before sharing; field names and formats are preserved):

# 1) Listings — confirms auth + provides listing_id and channel
curl -s -H "X-Integration-Api-Key: $WHEELHOUSE_API_KEY" \
  "https://api.usewheelhouse.com/ss_api/v1/listings?per_page=5&page=1" | head -c 4000

# 2) Reservations — THE blocking one (full JSON for 2–3 reservations)
curl -s -H "X-Integration-Api-Key: $WHEELHOUSE_API_KEY" \
  "https://api.usewheelhouse.com/ss_api/v1/listings/<LISTING_ID>/reservations?channel=<CHANNEL>" | head -c 6000

# 3) Price recommendations — for gap_night_check, lower priority
curl -s -H "X-Integration-Api-Key: $WHEELHOUSE_API_KEY" \
  "https://api.usewheelhouse.com/ss_api/v1/listings/<LISTING_ID>/price_recommendations?channel=<CHANNEL>" | head -c 4000

The rate limit is 60 requests/min — the server runs requests serially and backs off on 429 responses (2 s / 4 s / 8 s).

Core math

net_per_night  = (gross − cleaning − laundry − travel) / available nights
available      = booked nights + gap nights in the analysis window
gap_floor      = turnover cost + travel + MIN_MARGIN
leak           = Σ(€ of net-negative bookings) and % of booked nights

All data shipped with the repo and the package is synthetic — no real customer or property data is included.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选