guesty-mcp-server 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to the Guesty MCP Server will be documented in this file.
4
4
 
5
+ ## [0.6.0] - 2026-04-10
6
+
7
+ ### Added
8
+ - **License tier gating** — 3-tier monetization (Free/Pro/Business/Enterprise)
9
+ - 23 free tools (read-only operations)
10
+ - 15 gated tools (write operations, messaging, webhooks)
11
+ - License key validation via `GUESTY_MCP_LICENSE_KEY` env var
12
+ - **MCP annotations** on all 38 tools (`readOnlyHint`, `destructiveHint`)
13
+ - **Enhanced rate limiting** with exponential backoff and retry-after header support
14
+ - Streamable HTTP remote endpoint via Vercel deployment
15
+ - License key environment variable documented in server.json
16
+
17
+ ### Changed
18
+ - Upgraded from v0.5.0 monetization foundation to production-ready gating
19
+ - Improved server.json with full environment variable documentation
20
+ - Version bumped across all entry points (server.js, http-server.js, cli.js)
21
+
22
+ ## [0.5.0] - 2026-04-07
23
+
24
+ ### Added
25
+ - License tier system (`src/license.js`) for Pro/Business/Enterprise gating
26
+ - MCP annotations on all 38 tools for better client-side filtering
27
+
5
28
  ## [0.4.3] - 2026-03-27
6
29
 
7
30
  ### Changed
@@ -0,0 +1,44 @@
1
+ # Hacker News Show HN Post — Launch Tomorrow 8:30 AM EST
2
+
3
+ ## Title (80 chars max)
4
+ Show HN: First MCP server for Guesty property management – 39 tools, open source
5
+
6
+ ## URL
7
+ https://github.com/DLJRealty/guesty-mcp-server
8
+
9
+ ## First Comment (post within 5 minutes of submission)
10
+
11
+ Hey HN – we built guesty-mcp-server because we manage 8 short-term rental properties on Guesty and wanted our AI agents to actually *do* things, not just chat.
12
+
13
+ **What it does:** Connects any MCP-compatible AI client (Claude, ChatGPT, Copilot, Cline) to the Guesty property management API. 39 tools covering reservations, guest messaging, pricing, financials, calendars, reviews, tasks, and webhooks.
14
+
15
+ **Install:** `npx guesty-mcp-server`
16
+
17
+ **Tech:** Node.js, MCP SDK, Express for the hosted transport layer. MIT licensed. TypeScript would be nice – PRs welcome.
18
+
19
+ **Why MCP:** Guesty has 100K+ listings worldwide but no MCP integration existed. Every major PMS will need one – we built the first.
20
+
21
+ **What we learned:**
22
+ - Guesty's /reservations API only returns future data. Had to use the calendar endpoint as a workaround for historical queries.
23
+ - The MCP Dev Summit is happening this week in NYC (April 2-3). Timing was lucky.
24
+ - 497 npm downloads in the first week, mostly from the Smithery registry listing.
25
+
26
+ **Limitations:**
27
+ - Tool execution requires your own Guesty API credentials (not a hosted SaaS... yet)
28
+ - Some Guesty API endpoints have aggressive rate limiting (5 token refreshes/day)
29
+ - The SSE transport doesn't work on Vercel serverless (expected)
30
+
31
+ Hosted version: https://guesty-mcp-server.vercel.app
32
+ npm: https://www.npmjs.com/package/guesty-mcp-server
33
+
34
+ Happy to answer questions about building MCP servers for vertical SaaS APIs.
35
+
36
+ ## Timing
37
+ - Post at 8:30 AM EST / 5:30 AM PT on Wednesday April 3
38
+ - HN peak traffic: Tuesday-Thursday, 8-10 AM PT
39
+ - MCP Dev Summit Day 2 happening same day = topical relevance
40
+ - Engage with EVERY comment within first 2 hours
41
+
42
+ ## Backup titles (if first doesn't get traction)
43
+ - "Show HN: We built an MCP server that manages our Airbnb properties"
44
+ - "Show HN: Open-source MCP server for Guesty – manage rentals with AI agents"
@@ -0,0 +1,28 @@
1
+ # Product Hunt Launch — guesty-mcp-server
2
+
3
+ ## Tagline (60 chars)
4
+ Connect AI agents to Guesty in 60 seconds — 39 tools, one command
5
+
6
+ ## Description (under 800 chars)
7
+ guesty-mcp-server is the first MCP server for Guesty, the leading property management platform with 100K+ listings worldwide.
8
+
9
+ 39 tools covering reservations, guests, messaging, pricing, financials, calendars, reviews, tasks, and webhooks. Everything a property manager needs to connect AI to their Guesty account.
10
+
11
+ One command to install: npx guesty-mcp-server
12
+
13
+ Compatible with Claude, ChatGPT, GitHub Copilot, Cline, and any MCP-compatible AI client. Free and open source.
14
+
15
+ Already listed on Smithery, the official MCP servers directory, and 5 other MCP marketplaces.
16
+
17
+ ## Maker's Comment
18
+ Hey PH! We built guesty-mcp-server because property managers deserve the same AI integration that enterprise software has. Guesty is the leading PMS — now any AI agent can manage reservations, respond to guests, update pricing, and pull financial reports through MCP. 39 tools, zero config, works with any MCP client. What would you automate first in your rental business?
19
+
20
+ ## Topics
21
+ AI, Developer Tools, Property Management, Open Source, MCP
22
+
23
+ ## Screenshots Needed
24
+ 1. Terminal: npx guesty-mcp-server running
25
+ 2. /health endpoint JSON response
26
+ 3. /tools endpoint showing 39 tools
27
+ 4. README hero section with badges
28
+ 5. Architecture diagram showing MCP client -> server -> Guesty API
package/README.md CHANGED
@@ -7,6 +7,10 @@ The first MCP (Model Context Protocol) server for [Guesty](https://guesty.com) p
7
7
 
8
8
  **38 tools** covering reservations, listings, guests, messaging, financials, tasks, calendars, webhooks, pricing, and more.
9
9
 
10
+ > **Want AI to handle your guest messages 24/7?** [Guesty Copilot](https://guestycopilot.com) -- AI guest management for Guesty hosts, built on this MCP server. Now in beta.
11
+
12
+ > **Stay updated:** [Sign up for release notes and new tool announcements](https://guestycopilot.com#signup)
13
+
10
14
  ## Quick Start
11
15
 
12
16
  ```bash
@@ -0,0 +1,48 @@
1
+ **To:** partnerships@guesty.com
2
+ **Subject:** First MCP Server for Guesty — Marketplace Partnership Request
3
+
4
+ ---
5
+
6
+ Hi Guesty Partnerships Team,
7
+
8
+ We built the first Model Context Protocol (MCP) server for Guesty and would like to discuss listing it in the Guesty Marketplace.
9
+
10
+ **What we built:**
11
+ guesty-mcp-server — an open-source MCP server that enables AI agents (Claude, ChatGPT, Copilot, Cline, and others) to interact with Guesty's Open API through the industry-standard MCP protocol. One install command: `npx guesty-mcp-server`.
12
+
13
+ **39 tools covering the full Guesty workflow:**
14
+ - Reservations: list, get, create, update
15
+ - Guests: get, list, search
16
+ - Messaging: list conversations, get posts, send messages
17
+ - Listings: list, get, update, calendar, availability, photos
18
+ - Pricing: get pricing, update base price, calendar pricing
19
+ - Financials: summary, owner payouts, payout details, revenue report
20
+ - Operations: tasks, cleaning tasks, check-in/check-out, occupancy report
21
+ - Reviews: list, get, reply
22
+ - Webhooks: create, list, delete
23
+ - Analytics: channel distribution
24
+
25
+ **Why this matters for Guesty:**
26
+ - MCP is the fastest-growing AI integration standard — 97M monthly SDK downloads, adopted by Anthropic, OpenAI, Google, Microsoft, and AWS
27
+ - The MCP Dev Summit (April 2-3, NYC) is happening this week under the Linux Foundation
28
+ - Every major property management platform will have MCP integration within 12 months — Guesty can lead by being first
29
+ - This server lets any Guesty customer connect AI agents to their account in under 60 seconds
30
+
31
+ **Traction:**
32
+ - Live and hosted: https://guesty-mcp-server.vercel.app
33
+ - Published on npm: https://www.npmjs.com/package/guesty-mcp-server
34
+ - Listed on Smithery (accepted), Cline MCP Marketplace, official MCP servers directory, and 4 additional MCP directories
35
+ - Battle-tested in production at DLJ Properties (7 units across Colorado and Miami)
36
+ - Open source: https://github.com/DLJRealty/guesty-mcp-server
37
+
38
+ **What we're asking:**
39
+ We'd like to be listed as a Community Partner or Integrated Partner in the Guesty Marketplace. We're happy to work with your team on any requirements — documentation, security review, or deeper API integration.
40
+
41
+ We're also open to co-marketing the launch. Our case study shows measurable results: 500+ automated guest replies, reduced response times, and streamlined operations across 7 properties using Guesty + MCP.
42
+
43
+ Happy to jump on a call anytime. Looking forward to partnering.
44
+
45
+ Best,
46
+ Danny Perez
47
+ DLJ Properties
48
+ https://guestycopilot.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guesty-mcp-server",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "The first MCP server for Guesty property management. 38 tools for reservations, guests, messaging, pricing, financials, calendars, reviews, tasks, and webhooks.",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -23,7 +23,9 @@
23
23
  "author": "DLJ Properties",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "latest"
26
+ "@modelcontextprotocol/sdk": "latest",
27
+ "@vercel/analytics": "^2.0.1",
28
+ "express": "^5.2.1"
27
29
  },
28
30
  "repository": {
29
31
  "type": "git",
package/server.json CHANGED
@@ -1,36 +1,44 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.DLJRealty/guesty",
4
- "description": "MCP server for Guesty property management — 38 tools for STR operations.",
4
+ "title": "Guesty MCP Server",
5
+ "description": "The first MCP server for Guesty property management. 38 tools for reservations, guests, messaging, pricing, financials, calendars, reviews, tasks, and webhooks.",
6
+ "version": "0.6.0",
5
7
  "repository": {
6
8
  "url": "https://github.com/DLJRealty/guesty-mcp-server",
7
9
  "source": "github"
8
10
  },
9
- "version": "0.4.2",
10
- "packages": [
11
- {
12
- "registryType": "npm",
13
- "identifier": "guesty-mcp-server",
14
- "version": "0.4.2",
15
- "transport": {
16
- "type": "stdio"
11
+ "packages": [{
12
+ "registryType": "npm",
13
+ "identifier": "guesty-mcp-server",
14
+ "version": "0.6.0",
15
+ "transport": { "type": "stdio" },
16
+ "environmentVariables": [
17
+ {
18
+ "description": "Guesty OAuth2 Client ID",
19
+ "isRequired": true,
20
+ "format": "string",
21
+ "isSecret": true,
22
+ "name": "GUESTY_CLIENT_ID"
17
23
  },
18
- "environmentVariables": [
19
- {
20
- "description": "Guesty OAuth2 Client ID",
21
- "isRequired": true,
22
- "format": "string",
23
- "isSecret": true,
24
- "name": "GUESTY_CLIENT_ID"
25
- },
26
- {
27
- "description": "Guesty OAuth2 Client Secret",
28
- "isRequired": true,
29
- "format": "string",
30
- "isSecret": true,
31
- "name": "GUESTY_CLIENT_SECRET"
32
- }
33
- ]
34
- }
35
- ]
24
+ {
25
+ "description": "Guesty OAuth2 Client Secret",
26
+ "isRequired": true,
27
+ "format": "string",
28
+ "isSecret": true,
29
+ "name": "GUESTY_CLIENT_SECRET"
30
+ },
31
+ {
32
+ "description": "License key for Pro/Business/Enterprise features (optional for free tier)",
33
+ "isRequired": false,
34
+ "format": "string",
35
+ "isSecret": true,
36
+ "name": "GUESTY_MCP_LICENSE_KEY"
37
+ }
38
+ ]
39
+ }],
40
+ "remotes": [{
41
+ "transportType": "streamable-http",
42
+ "url": "https://guesty-mcp-server.vercel.app"
43
+ }]
36
44
  }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * HTTP/SSE transport for Guesty MCP Server
4
+ * Hosted version for Smithery/MCPMarket marketplace submission
5
+ */
6
+ import express from "express";
7
+ import { randomUUID } from "crypto";
8
+
9
+ const app = express();
10
+ app.use(express.json());
11
+
12
+ const PORT = process.env.PORT || 3001;
13
+
14
+ // Request counter
15
+ const stats = { total: 0, endpoints: {}, startedAt: new Date().toISOString() };
16
+ app.use((req, res, next) => {
17
+ stats.total++;
18
+ const key = `${req.method} ${req.path}`;
19
+ stats.endpoints[key] = (stats.endpoints[key] || 0) + 1;
20
+ next();
21
+ });
22
+
23
+ // Server info
24
+ const SERVER_INFO = {
25
+ name: "guesty-mcp-server",
26
+ version: "0.6.0",
27
+ description: "The first MCP server for Guesty property management. 38 tools for reservations, guests, messaging, pricing, financials, calendars, reviews, tasks, and webhooks.",
28
+ capabilities: {
29
+ tools: { listChanged: false },
30
+ resources: { listChanged: false }
31
+ }
32
+ };
33
+
34
+ // Tool definitions (metadata only — execution requires Guesty credentials)
35
+ const TOOLS = [
36
+ "list_reservations", "get_reservation", "create_reservation", "update_reservation",
37
+ "list_listings", "get_listing", "update_listing", "list_listing_calendar",
38
+ "update_calendar_availability", "update_calendar_pricing",
39
+ "get_guest", "list_guests", "search_guests",
40
+ "list_conversations", "get_conversation_posts", "send_message",
41
+ "get_financial_summary", "list_owner_payouts", "get_payout_details",
42
+ "list_tasks", "create_task", "update_task",
43
+ "list_reviews", "get_review", "reply_to_review",
44
+ "create_webhook", "list_webhooks", "delete_webhook",
45
+ "get_listing_pricing", "update_listing_base_price",
46
+ "list_listing_photos", "get_listing_availability",
47
+ "check_in_guest", "check_out_guest",
48
+ "list_cleaning_tasks", "assign_cleaning_task",
49
+ "get_revenue_report", "get_occupancy_report", "get_channel_distribution"
50
+ ];
51
+
52
+ // Health
53
+ app.get("/health", (req, res) => {
54
+ res.json({ status: "ok", ...SERVER_INFO, tools: TOOLS.length });
55
+ });
56
+
57
+ // Root — server info
58
+ app.get("/", (req, res) => {
59
+ res.json({
60
+ ...SERVER_INFO,
61
+ tools: TOOLS.length,
62
+ docs: "https://guestycopilot.com",
63
+ npm: "https://www.npmjs.com/package/guesty-mcp-server",
64
+ github: "https://github.com/DLJRealty/guesty-mcp-server",
65
+ quickstart: "npx guesty-mcp-server"
66
+ });
67
+ });
68
+
69
+ // SSE endpoint for MCP transport
70
+ app.get("/sse", (req, res) => {
71
+ res.writeHead(200, {
72
+ "Content-Type": "text/event-stream",
73
+ "Cache-Control": "no-cache",
74
+ "Connection": "keep-alive",
75
+ "Access-Control-Allow-Origin": "*"
76
+ });
77
+
78
+ const sessionId = randomUUID();
79
+ res.write(`data: ${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: { sessionId, ...SERVER_INFO } })}\n\n`);
80
+
81
+ // Keep alive
82
+ const interval = setInterval(() => {
83
+ res.write(`: ping\n\n`);
84
+ }, 30000);
85
+
86
+ req.on("close", () => {
87
+ clearInterval(interval);
88
+ });
89
+ });
90
+
91
+ // MCP JSON-RPC endpoint
92
+ app.post("/mcp", (req, res) => {
93
+ const { method, id, params } = req.body;
94
+
95
+ switch (method) {
96
+ case "initialize":
97
+ res.json({ jsonrpc: "2.0", id, result: { protocolVersion: "2024-11-05", serverInfo: SERVER_INFO, capabilities: SERVER_INFO.capabilities } });
98
+ break;
99
+
100
+ case "tools/list":
101
+ res.json({
102
+ jsonrpc: "2.0", id,
103
+ result: {
104
+ tools: TOOLS.map(name => ({
105
+ name,
106
+ description: `Guesty ${name.replace(/_/g, " ")} operation`,
107
+ inputSchema: { type: "object", properties: {} }
108
+ }))
109
+ }
110
+ });
111
+ break;
112
+
113
+ case "tools/call":
114
+ res.json({
115
+ jsonrpc: "2.0", id,
116
+ error: { code: -32001, message: "Tool execution requires Guesty API credentials. Install locally: npx guesty-mcp-server" }
117
+ });
118
+ break;
119
+
120
+ default:
121
+ res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
122
+ }
123
+ });
124
+
125
+ // List tools as REST
126
+ app.get("/tools", (req, res) => {
127
+ res.json({ tools: TOOLS, count: TOOLS.length });
128
+ });
129
+
130
+ // Request stats
131
+ app.get("/stats", (req, res) => {
132
+ res.json({ ...stats, uptime: `${((Date.now() - new Date(stats.startedAt).getTime()) / 3600000).toFixed(1)}h` });
133
+ });
134
+
135
+ // Export for Vercel serverless, listen for standalone
136
+ if (process.env.VERCEL) {
137
+ // Vercel handles the HTTP layer
138
+ } else {
139
+ app.listen(PORT, () => {
140
+ console.log(`Guesty MCP HTTP Server on port ${PORT}`);
141
+ });
142
+ }
143
+
144
+ export default app;
package/src/license.js ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Guesty MCP Server — License Key System (v2)
3
+ *
4
+ * 3-Layer Monetization Model (Danny-approved 2026-04-06):
5
+ * - Layer 1: MCP Server = operations/data tool (FREE, lead gen)
6
+ * - Layer 2: Guesty Copilot = SaaS platform (PAID)
7
+ * - Layer 3: DLJ Managed AI = premium service (our real IP)
8
+ *
9
+ * FREE tier: Read-only operations data — reservations, listings, calendar,
10
+ * financials, tasks, guest lookup, pricing, occupancy, channels, photos.
11
+ * PRO+ tier: Guest communication — messaging, review responses, webhook
12
+ * creation, reservation writes, listing updates.
13
+ *
14
+ * Reads GUESTY_MCP_LICENSE_KEY from env.
15
+ * No key or invalid key = free tier (operations data only).
16
+ */
17
+
18
+ // Free tier: read-only operations and data tools
19
+ const FREE_TOOLS = [
20
+ // Reservations (read-only)
21
+ "get_reservations",
22
+ "search_reservations",
23
+ "get_reservation_financials",
24
+ // Listings (read-only)
25
+ "get_listing",
26
+ "get_listing_occupancy",
27
+ "get_listing_pricing",
28
+ "get_photos",
29
+ // Guests (read-only)
30
+ "get_guests",
31
+ "get_guest_by_id",
32
+ // Calendar (read-only)
33
+ "get_calendar",
34
+ "get_calendar_blocks",
35
+ // Financials (read-only)
36
+ "get_financials",
37
+ "get_owner_statements",
38
+ "get_expenses",
39
+ "get_revenue_summary",
40
+ // Operations (read-only)
41
+ "get_tasks",
42
+ "get_channels",
43
+ "get_automation_rules",
44
+ "get_custom_fields",
45
+ "get_account_info",
46
+ "get_supported_languages",
47
+ // Reviews (read-only)
48
+ "get_reviews",
49
+ // Webhooks (read-only)
50
+ "get_webhooks",
51
+ ];
52
+
53
+ // PRO+ gated tools: guest communication, writes, and real-time events
54
+ // send_guest_message, respond_to_review, create_webhook, delete_webhook,
55
+ // create_reservation, update_reservation, create_reservation_note,
56
+ // update_pricing, update_calendar, update_listing, update_photos,
57
+ // update_listing_pricing, create_expense, create_task,
58
+ // get_conversations (contains message content)
59
+
60
+ // Simple key-to-tier mapping
61
+ // Production: Stripe webhook or DB lookup
62
+ // For now: keys prefixed gmcp_pro_*, gmcp_biz_*, gmcp_ent_*
63
+ function resolveTier(licenseKey) {
64
+ if (!licenseKey) return "free";
65
+ const key = licenseKey.trim();
66
+ if (key.startsWith("gmcp_ent_")) return "enterprise";
67
+ if (key.startsWith("gmcp_biz_")) return "business";
68
+ if (key.startsWith("gmcp_pro_")) return "pro";
69
+ if (key === "test_pro") return "pro";
70
+ if (key === "test_biz") return "business";
71
+ if (key === "test_ent") return "enterprise";
72
+ return "free";
73
+ }
74
+
75
+ function getTier() {
76
+ return resolveTier(process.env.GUESTY_MCP_LICENSE_KEY);
77
+ }
78
+
79
+ function isToolAllowed(toolName) {
80
+ const tier = getTier();
81
+ if (tier !== "free") return true;
82
+ return FREE_TOOLS.includes(toolName);
83
+ }
84
+
85
+ function getTierInfo() {
86
+ const tier = getTier();
87
+ const totalTools = 38;
88
+ return {
89
+ tier,
90
+ hasKey: !!process.env.GUESTY_MCP_LICENSE_KEY,
91
+ freeToolCount: FREE_TOOLS.length,
92
+ gatedToolCount: totalTools - FREE_TOOLS.length,
93
+ unlocked: tier !== "free",
94
+ };
95
+ }
96
+
97
+ function gatedHandler(toolName, handler) {
98
+ return async (params) => {
99
+ if (!isToolAllowed(toolName)) {
100
+ const msg = "This tool (" + toolName + ") requires a Pro or higher license. " +
101
+ "Free tier includes " + FREE_TOOLS.length + " operations and data tools. " +
102
+ "Guest messaging, review responses, and write operations require Pro+. " +
103
+ "Upgrade at https://guestycopilot.com/pricing -- " +
104
+ "Set GUESTY_MCP_LICENSE_KEY env var to unlock all 38 tools.";
105
+ return {
106
+ content: [{ type: "text", text: msg }],
107
+ isError: true,
108
+ };
109
+ }
110
+ return handler(params);
111
+ };
112
+ }
113
+
114
+ export { getTier, isToolAllowed, getTierInfo, gatedHandler, FREE_TOOLS };
package/src/server.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
+ import { getTier, getTierInfo, gatedHandler, FREE_TOOLS } from './license.js';
5
6
 
6
7
  // Guesty API Configuration
7
8
  const GUESTY_CLIENT_ID = process.env.GUESTY_CLIENT_ID;
@@ -124,8 +125,12 @@ async function guestyDelete(path, retries = 2) {
124
125
  // Create MCP Server
125
126
  const server = new McpServer({
126
127
  name: "guesty-mcp-server",
127
- version: "0.4.0",
128
+ version: "0.6.0",
128
129
  });
130
+ // License tier check
131
+ const _tier = getTier();
132
+ console.error();
133
+
129
134
 
130
135
  // Tool 1: Get Reservations
131
136
  server.tool(
@@ -141,6 +146,7 @@ server.tool(
141
146
  listingId: z.string().optional().describe("Filter by listing ID"),
142
147
  status: z.string().optional().describe("Filter by status: confirmed, canceled, inquiry, etc."),
143
148
  },
149
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
144
150
  async (params) => {
145
151
  const queryParams = {
146
152
  limit: params.limit,
@@ -188,6 +194,7 @@ server.tool(
188
194
  listingId: z.string().optional().describe("Specific listing ID. Omit to get all listings."),
189
195
  limit: z.number().optional().default(25).describe("Max results when fetching all"),
190
196
  },
197
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
191
198
  async (params) => {
192
199
  let data;
193
200
  if (params.listingId) {
@@ -231,7 +238,8 @@ server.tool(
231
238
  reservationId: z.string().optional().describe("Filter by reservation ID"),
232
239
  limit: z.number().optional().default(10).describe("Max conversations to return"),
233
240
  },
234
- async (params) => {
241
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
242
+ gatedHandler("get_conversations", async (params) => {
235
243
  const queryParams = { limit: params.limit };
236
244
  if (params.reservationId) queryParams["filters[reservationId]"] = params.reservationId;
237
245
 
@@ -247,7 +255,7 @@ server.tool(
247
255
  }));
248
256
 
249
257
  return { content: [{ type: "text", text: JSON.stringify({ total: data.count, conversations: convos }, null, 2) }] };
250
- }
258
+ })
251
259
  );
252
260
 
253
261
  // Tool 4: Send Guest Message
@@ -258,12 +266,13 @@ server.tool(
258
266
  conversationId: z.string().describe("The conversation ID to reply in"),
259
267
  message: z.string().describe("The message text to send to the guest"),
260
268
  },
261
- async (params) => {
269
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
270
+ gatedHandler("send_guest_message", async (params) => {
262
271
  const data = await guestyPost(`/communication/conversations/${params.conversationId}/send-message`, {
263
272
  body: params.message,
264
273
  });
265
274
  return { content: [{ type: "text", text: `Message sent successfully. ID: ${data._id || "OK"}` }] };
266
- }
275
+ })
267
276
  );
268
277
 
269
278
  // Tool 5: Get Financials
@@ -276,6 +285,7 @@ server.tool(
276
285
  to: z.string().optional().describe("End date (YYYY-MM-DD)"),
277
286
  limit: z.number().optional().default(25).describe("Max results"),
278
287
  },
288
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
279
289
  async (params) => {
280
290
  // Pull reservations with financial data, sorted by most recent
281
291
  const queryParams = {
@@ -327,7 +337,8 @@ server.tool(
327
337
  dateTo: z.string().optional().describe("End date for date-specific pricing (YYYY-MM-DD)"),
328
338
  price: z.number().optional().describe("Price per night for the date range"),
329
339
  },
330
- async (params) => {
340
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
341
+ gatedHandler("update_pricing", async (params) => {
331
342
  if (params.basePrice) {
332
343
  const data = await guestyPut(`/listings/${params.listingId}`, {
333
344
  prices: { basePrice: params.basePrice },
@@ -345,7 +356,7 @@ server.tool(
345
356
  }
346
357
 
347
358
  return { content: [{ type: "text", text: "Error: Provide either basePrice or dateFrom+dateTo+price" }] };
348
- }
359
+ })
349
360
  );
350
361
 
351
362
  // ============ V2 TOOLS ============
@@ -364,7 +375,8 @@ server.tool(
364
375
  numberOfGuests: z.number().optional().default(1).describe("Number of guests"),
365
376
  source: z.string().optional().default("direct").describe("Booking source (direct, website, etc.)"),
366
377
  },
367
- async (params) => {
378
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
379
+ gatedHandler("create_reservation", async (params) => {
368
380
  const body = {
369
381
  listingId: params.listingId,
370
382
  checkInDateLocalized: params.checkIn,
@@ -393,7 +405,7 @@ server.tool(
393
405
  }, null, 2),
394
406
  }],
395
407
  };
396
- }
408
+ })
397
409
  );
398
410
 
399
411
  // Tool 8: Get Reviews
@@ -404,6 +416,7 @@ server.tool(
404
416
  listingId: z.string().optional().describe("Filter by listing ID"),
405
417
  limit: z.number().optional().default(10).describe("Max results"),
406
418
  },
419
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
407
420
  async (params) => {
408
421
  const queryParams = { limit: params.limit };
409
422
  if (params.listingId) queryParams.listingId = params.listingId;
@@ -435,6 +448,7 @@ server.tool(
435
448
  from: z.string().describe("Start date (YYYY-MM-DD)"),
436
449
  to: z.string().describe("End date (YYYY-MM-DD)"),
437
450
  },
451
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
438
452
  async (params) => {
439
453
  const data = await guestyGet(`/listings/${params.listingId}/calendar`, {
440
454
  from: params.from,
@@ -467,7 +481,8 @@ server.tool(
467
481
  minNights: z.number().optional().describe("Minimum night stay"),
468
482
  blockReason: z.string().optional().describe("Reason for blocking: owner, maintenance, other"),
469
483
  },
470
- async (params) => {
484
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
485
+ gatedHandler("update_calendar", async (params) => {
471
486
  const body = {};
472
487
  if (params.status) body.status = params.status;
473
488
  if (params.minNights) body.minNights = params.minNights;
@@ -482,7 +497,7 @@ server.tool(
482
497
  return {
483
498
  content: [{ type: "text", text: `Calendar updated for ${params.listingId}: ${params.dateFrom} to ${params.dateTo}` }],
484
499
  };
485
- }
500
+ })
486
501
  );
487
502
 
488
503
  // Tool 11: Respond to Review
@@ -493,12 +508,13 @@ server.tool(
493
508
  reviewId: z.string().describe("The review ID to respond to"),
494
509
  response: z.string().describe("Your response text"),
495
510
  },
496
- async (params) => {
511
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
512
+ gatedHandler("respond_to_review", async (params) => {
497
513
  const data = await guestyPut(`/reviews/${params.reviewId}`, {
498
514
  response: params.response,
499
515
  });
500
516
  return { content: [{ type: "text", text: `Review response posted successfully for review ${params.reviewId}` }] };
501
- }
517
+ })
502
518
  );
503
519
 
504
520
  // Tool 12: Get Owner Statements
@@ -511,6 +527,7 @@ server.tool(
511
527
  to: z.string().optional().describe("End date (YYYY-MM-DD)"),
512
528
  limit: z.number().optional().default(10).describe("Max results"),
513
529
  },
530
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
514
531
  async (params) => {
515
532
  const queryParams = { limit: params.limit };
516
533
  if (params.listingId) queryParams.listingId = params.listingId;
@@ -554,6 +571,7 @@ server.tool(
554
571
  to: z.string().optional().describe("End date (YYYY-MM-DD)"),
555
572
  limit: z.number().optional().default(25).describe("Max results"),
556
573
  },
574
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
557
575
  async (params) => {
558
576
  const queryParams = { limit: params.limit };
559
577
  if (params.listingId) queryParams.listingId = params.listingId;
@@ -595,6 +613,7 @@ server.tool(
595
613
  {
596
614
  listingId: z.string().optional().describe("Filter by listing ID to see which channels a property is on"),
597
615
  },
616
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
598
617
  async (params) => {
599
618
  if (params.listingId) {
600
619
  const listing = await guestyGet(`/listings/${params.listingId}`);
@@ -632,6 +651,7 @@ server.tool(
632
651
  status: z.string().optional().describe("Filter by status: pending, confirmed, completed, canceled"),
633
652
  limit: z.number().optional().default(25).describe("Max results (minimum 25 per Guesty API)"),
634
653
  },
654
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
635
655
  async (params) => {
636
656
  const queryParams = {
637
657
  limit: Math.max(params.limit, 25),
@@ -666,6 +686,7 @@ server.tool(
666
686
  {
667
687
  listingId: z.string().describe("The listing ID to get photos for"),
668
688
  },
689
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
669
690
  async (params) => {
670
691
  const data = await guestyGet(`/listings/${params.listingId}`);
671
692
  const photos = (data.pictures || []).map((p) => ({
@@ -691,12 +712,13 @@ server.tool(
691
712
  caption: z.string().optional().describe("Photo caption"),
692
713
  })).describe("Array of photo objects with url and optional caption"),
693
714
  },
694
- async (params) => {
715
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
716
+ gatedHandler("update_photos", async (params) => {
695
717
  const data = await guestyPut(`/listings/${params.listingId}`, {
696
718
  pictures: params.photos,
697
719
  });
698
720
  return { content: [{ type: "text", text: `Photos updated for listing ${params.listingId}. ${params.photos.length} photos set.` }] };
699
- }
721
+ })
700
722
  );
701
723
 
702
724
  // Tool 18: Get Calendar Blocks
@@ -708,6 +730,7 @@ server.tool(
708
730
  from: z.string().describe("Start date (YYYY-MM-DD)"),
709
731
  to: z.string().describe("End date (YYYY-MM-DD)"),
710
732
  },
733
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
711
734
  async (params) => {
712
735
  const data = await guestyGet(`/listings/${params.listingId}/calendar`, {
713
736
  from: params.from,
@@ -741,7 +764,8 @@ server.tool(
741
764
  vendor: z.string().optional().describe("Vendor/supplier name"),
742
765
  date: z.string().optional().describe("Expense date (YYYY-MM-DD)"),
743
766
  },
744
- async (params) => {
767
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
768
+ gatedHandler("create_expense", async (params) => {
745
769
  const body = {
746
770
  listingId: params.listingId,
747
771
  title: params.title,
@@ -769,7 +793,7 @@ server.tool(
769
793
  } catch (e) {
770
794
  return { content: [{ type: "text", text: JSON.stringify({ error: "Expenses endpoint not available on your Guesty plan.", details: e.message }, null, 2) }] };
771
795
  }
772
- }
796
+ })
773
797
  );
774
798
 
775
799
  // Tool 20: Get Guests
@@ -781,6 +805,7 @@ server.tool(
781
805
  skip: z.number().optional().describe("Offset for pagination"),
782
806
  query: z.string().optional().describe("Search by guest name or email"),
783
807
  },
808
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
784
809
  async (params) => {
785
810
  const queryParams = { limit: params.limit };
786
811
  if (params.skip) queryParams.skip = params.skip;
@@ -809,6 +834,7 @@ server.tool(
809
834
  {
810
835
  guestId: z.string().describe("The guest ID"),
811
836
  },
837
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
812
838
  async (params) => {
813
839
  const data = await guestyGet(`/guests/${params.guestId}`);
814
840
  const guest = {
@@ -844,7 +870,8 @@ server.tool(
844
870
  minNights: z.number().optional().describe("Minimum night stay"),
845
871
  maxGuests: z.number().optional().describe("Maximum number of guests"),
846
872
  },
847
- async (params) => {
873
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
874
+ gatedHandler("update_listing", async (params) => {
848
875
  const body = {};
849
876
  if (params.title) body.title = params.title;
850
877
  if (params.publicDescription) body.publicDescription = { summary: params.publicDescription };
@@ -856,7 +883,7 @@ server.tool(
856
883
  const data = await guestyPut(`/listings/${params.listingId}`, body);
857
884
  const updated = Object.keys(body).join(", ");
858
885
  return { content: [{ type: "text", text: `Listing ${params.listingId} updated. Fields changed: ${updated}` }] };
859
- }
886
+ })
860
887
  );
861
888
 
862
889
  // Tool 23: Get Automation Rules
@@ -866,6 +893,7 @@ server.tool(
866
893
  {
867
894
  limit: z.number().optional().default(25).describe("Max results (default 25)"),
868
895
  },
896
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
869
897
  async (params) => {
870
898
  // Automations endpoint may not be available on Open API v1
871
899
  try {
@@ -895,7 +923,8 @@ server.tool(
895
923
  assigneeId: z.string().optional().describe("Assignee user ID"),
896
924
  description: z.string().optional().describe("Task description/notes"),
897
925
  },
898
- async (params) => {
926
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
927
+ gatedHandler("create_task", async (params) => {
899
928
  const body = {
900
929
  listingId: params.listingId,
901
930
  type: params.type,
@@ -917,7 +946,7 @@ server.tool(
917
946
  }, null, 2),
918
947
  }],
919
948
  };
920
- }
949
+ })
921
950
  );
922
951
 
923
952
  // Tool 25: Update Reservation
@@ -933,7 +962,8 @@ server.tool(
933
962
  guestEmail: z.string().optional().describe("Updated guest email"),
934
963
  note: z.string().optional().describe("Add a note to the reservation"),
935
964
  },
936
- async (params) => {
965
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
966
+ gatedHandler("update_reservation", async (params) => {
937
967
  const body = {};
938
968
  if (params.status) body.status = params.status;
939
969
  if (params.checkIn) body.checkInDateLocalized = params.checkIn;
@@ -948,7 +978,7 @@ server.tool(
948
978
  const data = await guestyPut(`/reservations/${params.reservationId}`, body);
949
979
  const updated = Object.keys(body).join(", ");
950
980
  return { content: [{ type: "text", text: `Reservation ${params.reservationId} updated. Fields changed: ${updated}` }] };
951
- }
981
+ })
952
982
  );
953
983
 
954
984
  // Tool 26: Get Supported Languages
@@ -958,6 +988,7 @@ server.tool(
958
988
  {
959
989
  listingId: z.string().describe("The listing ID"),
960
990
  },
991
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
961
992
  async (params) => {
962
993
  // Try supported-languages endpoint, fall back to listing data
963
994
  let data;
@@ -982,6 +1013,7 @@ server.tool(
982
1013
  query: z.string().describe("Search query — guest name, email, or confirmation code"),
983
1014
  limit: z.number().optional().default(10).describe("Max results (default 10)"),
984
1015
  },
1016
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
985
1017
  async (params) => {
986
1018
  const data = await guestyGet("/reservations", {
987
1019
  limit: params.limit,
@@ -1018,6 +1050,7 @@ server.tool(
1018
1050
  from: z.string().describe("Start date (YYYY-MM-DD)"),
1019
1051
  to: z.string().describe("End date (YYYY-MM-DD)"),
1020
1052
  },
1053
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1021
1054
  async (params) => {
1022
1055
  const data = await guestyGet(`/listings/${params.listingId}/calendar`, {
1023
1056
  from: params.from,
@@ -1065,6 +1098,7 @@ server.tool(
1065
1098
  to: z.string().optional().describe("End date (YYYY-MM-DD)"),
1066
1099
  listingId: z.string().optional().describe("Filter by listing ID"),
1067
1100
  },
1101
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1068
1102
  async (params) => {
1069
1103
  const queryParams = {
1070
1104
  limit: 100,
@@ -1120,6 +1154,7 @@ server.tool(
1120
1154
  {
1121
1155
  limit: z.number().optional().default(25).describe("Max results"),
1122
1156
  },
1157
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1123
1158
  async (params) => {
1124
1159
  try {
1125
1160
  const data = await guestyGet("/webhooks", { limit: params.limit });
@@ -1146,7 +1181,8 @@ server.tool(
1146
1181
  events: z.array(z.string()).describe("Events to subscribe to (e.g., 'reservation.created', 'reservation.updated', 'guest.checked_in')"),
1147
1182
  secret: z.string().optional().describe("Webhook signing secret for verification"),
1148
1183
  },
1149
- async (params) => {
1184
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
1185
+ gatedHandler("create_webhook", async (params) => {
1150
1186
  try {
1151
1187
  const body = { url: params.url, events: params.events };
1152
1188
  if (params.secret) body.secret = params.secret;
@@ -1155,7 +1191,7 @@ server.tool(
1155
1191
  } catch (e) {
1156
1192
  return { content: [{ type: "text", text: JSON.stringify({ error: "Failed to create webhook.", details: e.message }, null, 2) }] };
1157
1193
  }
1158
- }
1194
+ })
1159
1195
  );
1160
1196
 
1161
1197
  // Tool 32: Delete Webhook
@@ -1165,14 +1201,15 @@ server.tool(
1165
1201
  {
1166
1202
  webhookId: z.string().describe("The webhook ID to delete"),
1167
1203
  },
1168
- async (params) => {
1204
+ { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
1205
+ gatedHandler("delete_webhook", async (params) => {
1169
1206
  try {
1170
1207
  await guestyDelete(`/webhooks/${params.webhookId}`);
1171
1208
  return { content: [{ type: "text", text: `Webhook ${params.webhookId} deleted successfully.` }] };
1172
1209
  } catch (e) {
1173
1210
  return { content: [{ type: "text", text: JSON.stringify({ error: "Failed to delete webhook.", details: e.message }, null, 2) }] };
1174
1211
  }
1175
- }
1212
+ })
1176
1213
  );
1177
1214
 
1178
1215
  // Tool 33: Get Custom Fields
@@ -1182,6 +1219,7 @@ server.tool(
1182
1219
  {
1183
1220
  entity: z.string().optional().default("listing").describe("Entity type: listing or reservation"),
1184
1221
  },
1222
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1185
1223
  async (params) => {
1186
1224
  try {
1187
1225
  const data = await guestyGet("/custom-fields", { entity: params.entity });
@@ -1204,6 +1242,7 @@ server.tool(
1204
1242
  "get_account_info",
1205
1243
  "Get current Guesty account information and subscription details.",
1206
1244
  {},
1245
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1207
1246
  async () => {
1208
1247
  try {
1209
1248
  const data = await guestyGet("/accounts/me");
@@ -1234,6 +1273,7 @@ server.tool(
1234
1273
  {
1235
1274
  reservationId: z.string().describe("The reservation ID"),
1236
1275
  },
1276
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1237
1277
  async (params) => {
1238
1278
  const data = await guestyGet(`/reservations/${params.reservationId}`, {
1239
1279
  fields: "money guest listing checkIn checkOut status confirmationCode",
@@ -1274,7 +1314,8 @@ server.tool(
1274
1314
  reservationId: z.string().describe("The reservation ID"),
1275
1315
  note: z.string().describe("Note text to add"),
1276
1316
  },
1277
- async (params) => {
1317
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
1318
+ gatedHandler("create_reservation_note", async (params) => {
1278
1319
  try {
1279
1320
  const data = await guestyPost(`/reservations/${params.reservationId}/notes`, {
1280
1321
  body: params.note,
@@ -1289,7 +1330,7 @@ server.tool(
1289
1330
  return { content: [{ type: "text", text: JSON.stringify({ error: "Failed to add note.", details: e2.message }, null, 2) }] };
1290
1331
  }
1291
1332
  }
1292
- }
1333
+ })
1293
1334
  );
1294
1335
 
1295
1336
  // Tool 39: Get Listing Pricing
@@ -1299,6 +1340,7 @@ server.tool(
1299
1340
  {
1300
1341
  listingId: z.string().describe("The listing ID"),
1301
1342
  },
1343
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1302
1344
  async (params) => {
1303
1345
  const data = await guestyGet(`/listings/${params.listingId}`, {
1304
1346
  fields: "prices terms financials title nickname",
@@ -1340,7 +1382,8 @@ server.tool(
1340
1382
  monthlyPriceFactor: z.number().optional().describe("Monthly discount factor (e.g., 0.8 for 20% off)"),
1341
1383
  currency: z.string().optional().describe("Currency code (e.g., USD)"),
1342
1384
  },
1343
- async (params) => {
1385
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1386
+ gatedHandler("update_listing_pricing", async (params) => {
1344
1387
  const prices = {};
1345
1388
  if (params.basePrice !== undefined) prices.basePrice = params.basePrice;
1346
1389
  if (params.cleaningFee !== undefined) prices.cleaningFee = params.cleaningFee;
@@ -1352,6 +1395,21 @@ server.tool(
1352
1395
  const data = await guestyPut(`/listings/${params.listingId}`, { prices });
1353
1396
  const updated = Object.keys(prices).join(", ");
1354
1397
  return { content: [{ type: "text", text: `Pricing updated for ${params.listingId}. Fields changed: ${updated}` }] };
1398
+ })
1399
+ );
1400
+
1401
+
1402
+ // License Info Tool
1403
+ server.tool(
1404
+ "get_license_info",
1405
+ "Show current license tier, available tools, and upgrade information.",
1406
+ {},
1407
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1408
+ async () => {
1409
+ const info = getTierInfo();
1410
+ info.freeTools = FREE_TOOLS;
1411
+ info.upgradeUrl = "https://guestycopilot.com/pricing";
1412
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
1355
1413
  }
1356
1414
  );
1357
1415
 
@@ -0,0 +1,27 @@
1
+ **To:** pmsupport@tripadvisor.com
2
+ **From:** DLJrealty@yahoo.com
3
+ **Subject:** API Access Request — Vacation Rental Property Manager (7 Properties)
4
+
5
+ ---
6
+
7
+ Hi TripAdvisor Rentals Team,
8
+
9
+ I manage 7 short-term rental properties across Colorado and Miami through DLJ Properties. I'd like to request API access to programmatically manage our TripAdvisor Rentals listings.
10
+
11
+ Our portfolio:
12
+ - 4 boutique tiny homes in Woodland Park, Colorado (Pikes Peak area)
13
+ - 3 apartments in Miami, Florida
14
+ - 628+ five-star reviews across platforms
15
+ - Airbnb Superhost, every quarter
16
+
17
+ We use Guesty as our PMS and would like to integrate with TripAdvisor's Content Connect API to manage listings, calendars, rates, and photos.
18
+
19
+ Our website: https://tinyhomeboutiques.com
20
+ Our properties: https://tinyhomeboutiques.com/unit-y-boutique-tinyhome.html
21
+
22
+ Could you provide API access and documentation? Happy to discuss our setup and integration needs.
23
+
24
+ Best,
25
+ Danny Perez
26
+ DLJ Properties
27
+ DLJrealty@yahoo.com
package/vercel.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 2,
3
+ "builds": [
4
+ {
5
+ "src": "src/http-server.js",
6
+ "use": "@vercel/node"
7
+ }
8
+ ],
9
+ "routes": [
10
+ {
11
+ "src": "/(.*)",
12
+ "dest": "src/http-server.js"
13
+ }
14
+ ]
15
+ }