guesty-mcp-server 0.4.2 → 0.5.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 +60 -1
- package/PRODUCT-HUNT-LAUNCH.md +28 -0
- package/README.md +4 -0
- package/SMITHERY_DESCRIPTION.md +33 -0
- package/SMITHERY_VERIFICATION_REQUEST.md +37 -0
- package/package.json +5 -3
- package/publish-all.sh +44 -0
- package/server.json +14 -33
- package/src/LICENSE-INTEGRATION.md +61 -0
- package/src/http-server.js +144 -0
- package/src/license.js +114 -0
- package/src/server.js +88 -30
- package/vercel.json +15 -0
- package/.mcpregistry_github_token +0 -1
- package/.mcpregistry_registry_token +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Guesty MCP Server will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.3] - 2026-03-27
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Updated package description to reflect full 38-tool capability
|
|
9
|
+
- Updated CHANGELOG with complete version history (v0.3.0–v0.4.2)
|
|
10
|
+
|
|
11
|
+
## [0.4.2] - 2026-03-27
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- MCP Registry namespace case fix (`io.github.DLJRealty/guesty`)
|
|
15
|
+
- Added `.gitignore` entry for token files
|
|
16
|
+
|
|
17
|
+
## [0.4.1] - 2026-03-27
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Server.json description length exceeding MCP Registry limits
|
|
21
|
+
|
|
22
|
+
## [0.4.0] - 2026-03-27
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- MCP Registry `server.json` and Smithery `smithery.yaml` config
|
|
26
|
+
- `mcpName` field in package.json for registry discovery
|
|
27
|
+
- Expanded from 29 to **38 tools**:
|
|
28
|
+
- `get_reservation_financials` - Detailed financial breakdown per reservation
|
|
29
|
+
- `get_reservation_notes` - Internal notes on reservations
|
|
30
|
+
- `add_reservation_note` - Add notes to reservations
|
|
31
|
+
- `get_listing_pricing` - Pricing rules and rate plans
|
|
32
|
+
- `get_account_info` - Guesty account details
|
|
33
|
+
- `create_webhook` - Register webhooks for real-time events
|
|
34
|
+
- `delete_webhook` - Remove registered webhooks
|
|
35
|
+
- `get_custom_fields` - Custom field definitions
|
|
36
|
+
- `update_custom_fields` - Update custom field values
|
|
37
|
+
- Delete helper utility for webhook management
|
|
38
|
+
- Improved error handling across all tools
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- 5 failing tools identified and fixed via E2E test against live Guesty API
|
|
42
|
+
|
|
43
|
+
## [0.3.0] - 2026-03-26
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- Expanded from 15 to **29 tools**:
|
|
47
|
+
- `get_photos` - Property photo URLs and metadata
|
|
48
|
+
- `get_guest` - Guest profile details
|
|
49
|
+
- `get_guests` - Search and list guests
|
|
50
|
+
- `get_occupancy_stats` - Occupancy rates and statistics
|
|
51
|
+
- `get_revenue_stats` - Revenue analytics and trends
|
|
52
|
+
- And additional operational tools
|
|
53
|
+
- Docker support with `Dockerfile` and `docker-compose.yml`
|
|
54
|
+
- HTTP transport module for remote MCP access (non-stdio)
|
|
55
|
+
- Integration test suite (`tests/test-tools.js`) for all tools
|
|
56
|
+
- CLI tool (`guesty-cli`) for command-line usage
|
|
57
|
+
- Security guide (`SECURITY.md`)
|
|
58
|
+
- Health check endpoint for production monitoring
|
|
59
|
+
- Webhook handler module for real-time Guesty events (v3 prep)
|
|
60
|
+
- Multi-account design doc for v3 architecture
|
|
61
|
+
- GitHub Actions CI workflow
|
|
62
|
+
- Example configs for Claude Code and Docker Compose
|
|
63
|
+
|
|
5
64
|
## [0.2.0] - 2026-03-26
|
|
6
65
|
|
|
7
66
|
### Added
|
|
8
|
-
- `create_reservation` - Create direct bookings (website
|
|
67
|
+
- `create_reservation` - Create direct bookings (website to Guesty)
|
|
9
68
|
- `get_reviews` - Fetch guest reviews from all channels
|
|
10
69
|
- `get_calendar` - Check availability and pricing by date range
|
|
11
70
|
- `update_calendar` - Block/unblock dates, set minimum nights
|
|
@@ -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,33 @@
|
|
|
1
|
+
# Smithery Listing — Optimized Description (v2 — Operations Focus)
|
|
2
|
+
|
|
3
|
+
## Display Name
|
|
4
|
+
Guesty MCP Server
|
|
5
|
+
|
|
6
|
+
## Short Description (for search results)
|
|
7
|
+
The first MCP server for Guesty — automate property operations, reports, calendar sync, pricing, and occupancy monitoring for short-term rentals and vacation rental portfolios.
|
|
8
|
+
|
|
9
|
+
## Full Description (for server detail page)
|
|
10
|
+
Automate short-term rental operations with Guesty — the leading property management system (PMS). 39 production-ready tools for property managers, Airbnb hosts, VRBO operators, and hospitality teams managing vacation rentals, boutique hotels, and serviced apartments.
|
|
11
|
+
|
|
12
|
+
Reports & Revenue: Generate financial reports, owner statements, expense summaries, and revenue breakdowns across your entire portfolio. Track reservation-level financials and host payouts in real time.
|
|
13
|
+
|
|
14
|
+
Calendar & Availability: Sync calendars across all channels, manage date blocks, and monitor occupancy rates. Never miss a booking gap or double-booking risk.
|
|
15
|
+
|
|
16
|
+
Pricing Management: View and update nightly rates, cleaning fees, seasonal pricing, and weekly/monthly discounts. Optimize revenue across every listing.
|
|
17
|
+
|
|
18
|
+
Reservations & Guest Data: Search and filter reservations by date, status, channel, or listing. Look up guest details, contact info, and booking history.
|
|
19
|
+
|
|
20
|
+
Property Operations: Create and track maintenance tasks, view listing details, manage property photos, monitor automation rules, and check channel distribution status.
|
|
21
|
+
|
|
22
|
+
Reviews & Reputation: Monitor guest reviews across all platforms — track your rating trends and response rates.
|
|
23
|
+
|
|
24
|
+
Webhooks & Automation: Configure real-time event webhooks for check-ins, check-outs, new bookings, cancellations, and message events.
|
|
25
|
+
|
|
26
|
+
Free tier includes 23 operations and data tools. Pro tier unlocks guest messaging, review responses, and write operations. Production-tested across 7 properties. Supports stdio and HTTP/SSE transports. Open source, MIT licensed.
|
|
27
|
+
|
|
28
|
+
## Keywords Hit
|
|
29
|
+
property management, PMS, vacation rental, short-term rental, Airbnb, VRBO, Booking.com,
|
|
30
|
+
hospitality, operations, reports, revenue, calendar sync, pricing, occupancy monitoring,
|
|
31
|
+
task management, property operations, channel manager, reservations, financial reports,
|
|
32
|
+
owner statements, expenses, webhooks, automation, property manager, host, real estate,
|
|
33
|
+
boutique hotel, serviced apartment, cleaning fee, reviews, reputation management
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Smithery Verification Request — Guesty MCP Server
|
|
2
|
+
|
|
3
|
+
## To: Smithery Team
|
|
4
|
+
|
|
5
|
+
Subject: Verification Request — First-Ever Guesty MCP Server
|
|
6
|
+
|
|
7
|
+
Hi Smithery team,
|
|
8
|
+
|
|
9
|
+
We are requesting verification for our Guesty MCP Server — the first and only MCP integration for Guesty, the leading property management system serving 100,000+ properties worldwide.
|
|
10
|
+
|
|
11
|
+
### Why This Deserves Verification
|
|
12
|
+
|
|
13
|
+
1. **Category First-Mover**: No PMS (Property Management System) MCP server exists on Smithery. We are defining a new category.
|
|
14
|
+
|
|
15
|
+
2. **Production-Tested**: Running in production across 7 short-term rental properties handling real guest messages, pricing updates, and reservation management daily.
|
|
16
|
+
|
|
17
|
+
3. **Comprehensive Coverage**: 39 tools spanning reservations, guest messaging, pricing, financials, calendars, reviews, tasks, and webhooks — the most complete PMS integration available via MCP.
|
|
18
|
+
|
|
19
|
+
4. **Quality**: All tools include MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint), detailed parameter descriptions, and proper error handling.
|
|
20
|
+
|
|
21
|
+
5. **Published & Documented**: Available on npm (guesty-mcp-server v0.4.3, 500+ downloads), MIT licensed, with full documentation.
|
|
22
|
+
|
|
23
|
+
6. **Dual Transport**: Supports both stdio and HTTP/SSE (Streamable HTTP) transports for maximum compatibility.
|
|
24
|
+
|
|
25
|
+
### Server Details
|
|
26
|
+
- **npm**: https://www.npmjs.com/package/guesty-mcp-server
|
|
27
|
+
- **GitHub**: https://github.com/DLJRealty/guesty-mcp-server
|
|
28
|
+
- **Live Endpoint**: https://guesty-mcp-server.vercel.app
|
|
29
|
+
- **Namespace**: @dlj/guesty
|
|
30
|
+
|
|
31
|
+
### About Guesty
|
|
32
|
+
Guesty is the #1 property management platform for short-term rentals, used by property managers managing vacation rentals on Airbnb, VRBO, Booking.com, and direct booking channels. Bringing Guesty to the MCP ecosystem opens automation for an industry managing billions in rental revenue.
|
|
33
|
+
|
|
34
|
+
We would appreciate verification to help property managers discover this integration with confidence.
|
|
35
|
+
|
|
36
|
+
Best regards,
|
|
37
|
+
DLJ Properties Engineering Team
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guesty-mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP
|
|
3
|
+
"version": "0.5.0",
|
|
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": {
|
|
7
7
|
"guesty-mcp-server": "src/server.js",
|
|
@@ -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/publish-all.sh
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Guesty MCP Server v0.5.0 — Full Publish Pipeline
|
|
3
|
+
# Danny runs this once. Approves browser auths. All platforms go live.
|
|
4
|
+
|
|
5
|
+
export PATH=/opt/homebrew/bin:/opt/homebrew/opt/node/bin:/Users/dljrealty/.local/bin:$PATH
|
|
6
|
+
cd /Users/dljrealty/guesty-mcp-server
|
|
7
|
+
|
|
8
|
+
echo '=== STEP 0: npm Login (if needed) ==='
|
|
9
|
+
npm whoami 2>/dev/null || npm adduser
|
|
10
|
+
echo ''
|
|
11
|
+
|
|
12
|
+
echo '=== STEP 0.5: Commit & push v0.5.0 to GitHub (triggers Vercel deploy) ==='
|
|
13
|
+
git add package.json server.json src/server.js src/license.js
|
|
14
|
+
git commit -m 'v0.5.0: Add license tier gating and MCP annotations
|
|
15
|
+
|
|
16
|
+
- 23 free operations/data tools, 15 gated behind Pro+ (messaging, writes, webhooks)
|
|
17
|
+
- MCP annotations (readOnlyHint, destructiveHint, etc.) on all 38 tools
|
|
18
|
+
- License key system via GUESTY_MCP_LICENSE_KEY env var
|
|
19
|
+
- get_license_info tool shows current tier and available tools'
|
|
20
|
+
git push origin main
|
|
21
|
+
echo ''
|
|
22
|
+
|
|
23
|
+
echo '=== STEP 1: Publish v0.5.0 to npm ==='
|
|
24
|
+
npm publish
|
|
25
|
+
echo ''
|
|
26
|
+
|
|
27
|
+
echo '=== STEP 2: Publish to Smithery ==='
|
|
28
|
+
echo 'A browser will open for auth. Approve it.'
|
|
29
|
+
smithery auth login
|
|
30
|
+
smithery mcp publish 'https://guesty-mcp-server.vercel.app' -n @dlj/guesty
|
|
31
|
+
echo ''
|
|
32
|
+
|
|
33
|
+
echo '=== STEP 3: Publish to MCP Registry ==='
|
|
34
|
+
echo 'A browser will open for GitHub device auth. Enter the code shown.'
|
|
35
|
+
/Users/dljrealty/.local/bin/mcp-publisher login github
|
|
36
|
+
/Users/dljrealty/.local/bin/mcp-publisher publish
|
|
37
|
+
echo ''
|
|
38
|
+
|
|
39
|
+
echo '=== ALL DONE ==='
|
|
40
|
+
echo 'Verify:'
|
|
41
|
+
echo ' npm: https://www.npmjs.com/package/guesty-mcp-server'
|
|
42
|
+
echo ' Smithery: https://smithery.ai/server/@dlj/guesty'
|
|
43
|
+
echo ' Registry: curl https://registry.modelcontextprotocol.io/v0.1/servers?search=guesty'
|
|
44
|
+
echo ' Vercel: https://guesty-mcp-server.vercel.app (auto-deploys from GitHub push)'
|
package/server.json
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"\$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.DLJRealty/guesty",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
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
|
-
]
|
|
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.5.0",
|
|
7
|
+
"packages": [{
|
|
8
|
+
"registryType": "npm",
|
|
9
|
+
"identifier": "guesty-mcp-server",
|
|
10
|
+
"version": "0.5.0",
|
|
11
|
+
"transport": { "type": "stdio" }
|
|
12
|
+
}],
|
|
13
|
+
"remotes": [{
|
|
14
|
+
"transportType": "streamable-http",
|
|
15
|
+
"url": "https://guesty-mcp-server.vercel.app"
|
|
16
|
+
}]
|
|
36
17
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# License Key Integration Guide
|
|
2
|
+
|
|
3
|
+
## How it works
|
|
4
|
+
|
|
5
|
+
1. `license.js` reads `GUESTY_MCP_LICENSE_KEY` env var on startup
|
|
6
|
+
2. Maps key prefix to tier: `gmcp_pro_*` = Pro, `gmcp_biz_*` = Business, `gmcp_ent_*` = Enterprise
|
|
7
|
+
3. No key or invalid key = Free tier (5 read-only tools)
|
|
8
|
+
4. `gatedHandler()` wraps each tool's handler — returns upgrade message for gated tools
|
|
9
|
+
|
|
10
|
+
## Integration into server.js (2 changes)
|
|
11
|
+
|
|
12
|
+
### Change 1: Import at top of server.js
|
|
13
|
+
```js
|
|
14
|
+
import { getTier, getTierInfo, gatedHandler } from './license.js';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Change 2: Wrap each tool handler
|
|
18
|
+
Before:
|
|
19
|
+
```js
|
|
20
|
+
server.tool('send_guest_message', 'Send a message...', { ... }, async (params) => {
|
|
21
|
+
// handler code
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
After:
|
|
26
|
+
```js
|
|
27
|
+
server.tool('send_guest_message', 'Send a message...', { ... }, gatedHandler('send_guest_message', async (params) => {
|
|
28
|
+
// handler code — unchanged
|
|
29
|
+
}));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Change 3 (optional): Add tier info tool
|
|
33
|
+
```js
|
|
34
|
+
server.tool('get_license_info', 'Show current license tier and available tools', {}, async () => {
|
|
35
|
+
const info = getTierInfo();
|
|
36
|
+
return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Testing
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Free tier (no key)
|
|
44
|
+
unset GUESTY_MCP_LICENSE_KEY
|
|
45
|
+
npx guesty-mcp-server
|
|
46
|
+
# Only 5 tools work, others return upgrade message
|
|
47
|
+
|
|
48
|
+
# Pro tier (test key)
|
|
49
|
+
GUESTY_MCP_LICENSE_KEY=test_pro npx guesty-mcp-server
|
|
50
|
+
# All 38 tools unlocked
|
|
51
|
+
|
|
52
|
+
# Pro tier (production key)
|
|
53
|
+
GUESTY_MCP_LICENSE_KEY=gmcp_pro_abc123 npx guesty-mcp-server
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Key Format
|
|
57
|
+
- Free: no key
|
|
58
|
+
- Pro: `gmcp_pro_<random>`
|
|
59
|
+
- Business: `gmcp_biz_<random>`
|
|
60
|
+
- Enterprise: `gmcp_ent_<random>`
|
|
61
|
+
- Test keys: `test_pro`, `test_biz`, `test_ent`
|
|
@@ -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.4.3",
|
|
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.
|
|
128
|
+
version: "0.5.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/vercel.json
ADDED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ghu_erLfGY4zAYVbjzUz0jwdlWjPzYIRaP0Kql1T
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzQ1NzkyNTksIm5iZiI6MTc3NDU3ODk1OSwiaWF0IjoxNzc0NTc4OTU5LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6IkRMSlJlYWx0eSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIuRExKUmVhbHR5LyoifV19.U8Lmee0qloF-2ugZuRdpi7Wnu-HeODY6kfMmztM1TamM4mWppFfytEELC9EM6nTs6Sq_y1HSKOmHiWOhODVYDg","expires_at":1774579259}
|