Book hotels over MCP. Pay over x402. 3M+ properties in 200+ countries, USDC on Base.
The com.trip1/mcp MCP server provides tools to help you book hotels over MCP, including payment handling described as โPay over x402โ. It references 3M+ properties across 200+ countries and indicates USDC on Base for payments. The server exposes 4 tools.
๐ ๏ธ Key Features
Hotel booking via MCP
Payment flow described as โPay over x402โ
Coverage: 3M+ properties in 200+ countries
Payment asset: USDC on Base
Tooling: 4 tools
๐ Use Cases
Booking hotels programmatically through an MCP client
Integrating hotel inventory search/selection with MCP-based payment handling (x402)
Search for available hotels by destination and dates. Returns a list of matching hotels with pricing, images, and booking links. Occupancy is fixed at 2 adults in one room and cannot be set. If the user asked for a different party size, tell them these prices are for 2 adults rather than presenting them as a quote for their group. Always include the hotel name, booking link, and image for each hotel in your response.
Parameters6
destination
string
required
Destination name (e.g., 'London', 'Paris, France', 'Vilnius')
check_in
string
required
Check-in date in YYYY-MM-DD format. Must be today or later in the destination's own timezone. Do not assume the current year โ the server instructions state today's date; a past date is rejected, and the rejection tells you the destination's today.
check_out
string
required
Check-out date in YYYY-MM-DD format. Must be after check_in.
sort_by
string
optional
Sort results by: price, rating, distance, or relevance (default)
sort_order
string
optional
Sort order: asc (default) or desc
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"destination": {
"type": "string",
"description": "Destination name (e.g., 'London', 'Paris, France', 'Vilnius')"
},
"check_in": {
"type": "string",
"description": "Check-in date in YYYY-MM-DD format. Must be today or later in the destination's own timezone. Do not assume the current year โ the server instructions state today's date; a past date is rejected, and the rejection tells you the destination's today."
},
"check_out": {
"type": "string",
"description": "Check-out date in YYYY-MM-DD format. Must be after check_in."
},
"sort_by": {
"type": "string",
"description": "Sort results by: price, rating, distance, or relevance (default)",
"enum": [
"relevance",
"price",
"rating",
"distance"
]
},
"sort_order": {
"type": "string",
"description": "Sort order: asc (default) or desc",
"enum": [
"asc",
"desc"
]
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"destination",
"check_in",
"check_out"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
get_hotel_details
Get rooms and rates for a hotel. Pass the hotel id from search_hotels results. Returns rate IDs needed for purchase_hotel. Each rate states its refundability and, when refundable, the cancellation deadline โ quote these rather than inferring refund terms. When prices_changed is true, the price search_hotels quoted for this hotel is no longer the one on offer โ tell the user prices changed rather than presenting these rates as the same quote. When the user picks a rate, call start_checkout with its id next -- it opens the booking form, so do not ask for guest details in chat.
Parameters2
id
string
required
The hotel id from search_hotels results
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The hotel id from search_hotels results"
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"id"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
start_checkout
Open the booking form for a rate the user picked from get_hotel_details. Shows the stay and the price, and lets the user enter the guest details and choose how to pay. On the first call, pass the user's own details you already know -- from this conversation or your memory of them -- so the form opens prefilled, and only those: never invent or guess any; the form asks for the rest and the user reviews every field before booking. If the user has not stated a title, pass title_suggestion instead: your best guess from their first name, which the form labels as a suggestion. If the user submits the form, it books the room itself (purchase_hotel) and tells you the booking reference: do not call purchase_hotel again for it. Call this again with more details to refill the form.
Parameters10
rate_id
string
required
Rate id from get_hotel_details
first_name
string
optional
Guest's first name, if known
last_name
string
optional
Guest's last name, if known
email
string
optional
Guest's email, if known
phone
string
optional
Guest's phone with country code, if known
title
string
optional
MR, MRS, MS or MISS, only if the user has stated it
title_suggestion
string
optional
When the user has not stated a title: your best guess from their first name. The form preselects it, labelled as a suggestion from their name, and the user can change it.
nationality
string
optional
Guest's nationality as ISO 3166-1 alpha-2, if known
payment_service
string
optional
Payment method the user already chose, if any
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"rate_id": {
"type": "string",
"description": "Rate id from get_hotel_details"
},
"first_name": {
"type": "string",
"description": "Guest's first name, if known"
},
"last_name": {
"type": "string",
"description": "Guest's last name, if known"
},
"email": {
"type": "string",
"description": "Guest's email, if known"
},
"phone": {
"type": "string",
"description": "Guest's phone with country code, if known"
},
"title": {
"type": "string",
"description": "MR, MRS, MS or MISS, only if the user has stated it",
"enum": [
"MR",
"MRS",
"MS",
"MISS"
]
},
"title_suggestion": {
"type": "string",
"enum": [
"MR",
"MRS",
"MS",
"MISS"
],
"description": "When the user has not stated a title: your best guess from their first name. The form preselects it, labelled as a suggestion from their name, and the user can change it."
},
"nationality": {
"type": "string",
"description": "Guest's nationality as ISO 3166-1 alpha-2, if known"
},
"payment_service": {
"type": "string",
"description": "Payment method the user already chose, if any",
"enum": [
"coingate",
"x402"
]
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"rate_id"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
purchase_hotel
Book a hotel room in chat, by providing guest details and a rate ID from get_hotel_details. Use start_checkout instead, which opens a booking form; call this only when the user says they cannot see or use that form. Creates a guest profile, adds the room to a cart, and initiates checkout. Ask the user how they want to pay before calling, and pass it as payment_service: 'x402' (USDC on Base, paid by an x402 payments tool you have) or 'coingate' (a checkout page the user pays in their browser, any crypto). If you have no x402 payments tool, use coingate. Returns a payment_url and the cart_url where the order can also be paid in a browser. The booking is confirmed once payment settles; poll get_order_details to check status.
Parameters10
rate_id
string
required
Signed rate ID from get_hotel_details response
first_name
string
required
Guest's first name
last_name
string
required
Guest's last name
email
string
required
Guest's email address
phone
string
required
Guest's phone number (e.g., +44123456789)
nationality
string
optional
Guest's nationality as ISO 3166-1 alpha-2 code (default: GB)
title
string
optional
Guest's title: MR, MRS, MS, or MISS (default: MR)
price_currency
string
optional
Payment currency (default: USD for x402, EUR for coingate). Supported: EUR, USD, GBP, PLN, CZK, HUF, SEK, NOK, DKK
payment_service
string
optional
Payment method the user chose: x402 (USDC on Base, needs an x402 payments tool; default) or coingate (browser checkout, any crypto)
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"rate_id": {
"type": "string",
"description": "Signed rate ID from get_hotel_details response"
},
"first_name": {
"type": "string",
"description": "Guest's first name"
},
"last_name": {
"type": "string",
"description": "Guest's last name"
},
"email": {
"type": "string",
"description": "Guest's email address"
},
"phone": {
"type": "string",
"description": "Guest's phone number (e.g., +44123456789)"
},
"nationality": {
"type": "string",
"description": "Guest's nationality as ISO 3166-1 alpha-2 code (default: GB)"
},
"title": {
"type": "string",
"description": "Guest's title: MR, MRS, MS, or MISS (default: MR)",
"enum": [
"MR",
"MRS",
"MS",
"MISS"
]
},
"price_currency": {
"type": "string",
"description": "Payment currency (default: USD for x402, EUR for coingate). Supported: EUR, USD, GBP, PLN, CZK, HUF, SEK, NOK, DKK"
},
"payment_service": {
"type": "string",
"description": "Payment method the user chose: x402 (USDC on Base, needs an x402 payments tool; default) or coingate (browser checkout, any crypto)",
"enum": [
"x402",
"coingate"
]
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"rate_id",
"first_name",
"last_name",
"email",
"phone"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
pay_order
Open CoinGate's checkout page for an unpaid CoinGate booking, so the user can pay in their browser. Pass the cart_id from purchase_hotel or the booking form. Where the client supports it, this asks the client to open the page for the user (URL elicitation); otherwise, and once the page has been offered, it returns the order with its payment link. Not for x402 bookings, which your payments tool pays.
Parameters2
cart_id
string
required
The cart_id from purchase_hotel or the booking form
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"cart_id": {
"type": "string",
"description": "The cart_id from purchase_hotel or the booking form"
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"cart_id"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
get_order_details
Get the current state of an order. Returns payment status, booking state, and item details. Poll until ready is true to confirm the booking is complete.
Parameters2
cart_id
string
required
The cart_id from purchase_hotel response
context
string
optional
Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user.
Raw schema
{
"type": "object",
"properties": {
"cart_id": {
"type": "string",
"description": "The cart_id from purchase_hotel response"
},
"context": {
"type": "string",
"maxLength": 200,
"description": "Why are you calling this tool? Describe the user's underlying goal in one short sentence โ not the tool name. Never include names, emails, phones, or prices. Always pass this. Used for analytics only; never shown to the user."
}
},
"required": [
"cart_id"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}
A plugin that lets agents book hotels on trip1 through the trip1 MCP server: about 3 million properties in 200+ countries, paid in USDC on Base over x402.
The MCP server and the skill come in together. Restart the session so Claude Code picks up the new MCP connection.
Claude Desktop
Two options.
As a local plugin. Download agents.zip from the latest release, then in Claude Desktop go to Customize โ Personal plugins โ Upload local plugin and drop the zip in.
As a connector. If you only want the MCP server without the skill, add the connector directly:
Cursor also loads this repo as a plugin through plugin.json and mcp.json. Clients that only speak stdio can bridge with npx -y mcp-remote https://trip1.com/api/mcp.
Codex CLI, Gemini CLI, any SKILL.md-compatible agent
bash
npx skills add trivial-corp/agents
This installs the skill. Add the MCP server separately via your client's MCP config (same JSON as above).
ChatGPT
Search for trip1 in the ChatGPT apps directory. Or add it as a custom connector in Settings โ Connectors โ Add, pointing at:
code
https://trip1.com/api/mcp
The skill doesn't apply here; ChatGPT doesn't load SKILL.md files. The tool descriptions in the MCP server carry enough intent for ChatGPT to drive the flow on its own.
Cowork and other MCP-aware clients
Any client that speaks remote MCP (Streamable HTTP) can add trip1 as a connector. Paste the same mcpServers block above, or the bare URL https://trip1.com/api/mcp if the client accepts URLs directly.
Local development
bash
git clone https://github.com/trivial-corp/agents
cd agents
claude --plugin-dir .
What the skill does
The skill is instructions, not code. It tells the agent:
when a hotel-booking intent is present and the skill should activate
which trip1 MCP tools to call, in what order, with which arguments
how to handle the x402 payment handshake and the CoinGate fallback
how to report results and recover from rate drops, payment failures, and polling timeouts
Paying on the agent's behalf
For fully hands-off agent payments, load an x402-capable wallet MCP alongside trip1. The simplest option:
bash
npx @coinbase/payments-mcp
Paying from code with the x402 SDK
@x402/fetch refuses any payment above $1 by default, and a hotel booking always costs more, so raise the cap. Pin the recipient too, so the client only ever pays trip1:
Call purchase_hotel with payment_service: "coingate". It returns a CoinGate checkout URL that a human finishes in a browser. CoinGate takes 120+ cryptocurrencies, including USDC.
Publishing to the MCP Registry
Bump version in server.json, then run mcp-publisher publish after mcp-publisher login dns --domain trip1.com. The DNS key lives with the trip1 team.
Releasing
Bump version in .claude-plugin/plugin.json and merge to main. The release.yml workflow tags vX.Y.Z, packages agents.zip (full plugin bundle) plus one zip per skill under skills/, and publishes them to a GitHub release.
To cut a release from an existing commit instead, push a tag manually: git tag -a vX.Y.Z -m "..." && git push origin vX.Y.Z. The same workflow handles it.