Connect
ChatGPT
- Open Wendily in ChatGPT and click Install plugin.
- Sign in to Wendily when asked, and click Allow.
- In a new chat, type @Wendily and tell it about your trip.
Claude
- Open Add custom connector in Claude, which has the name and URL filled in, and click Add.
- Click Connect, sign in to Wendily when asked, and click Allow.
Claude Code
claude mcp add --transport http wendily https://wendily.com/mcpThen run /mcp in a session to sign in. To use a key instead:
claude mcp add --transport http wendily https://wendily.com/mcp \
--header "Authorization: Bearer trips_sk_…"Other MCP clients
Add the server URL. If your client signs in over OAuth, it finds everything it needs from there, as long as its redirect URI is allowed. Otherwise, create a key on the Connections page and send it as a header. The exact format depends on your client; many use this one:
{
"mcpServers": {
"wendily": {
"url": "https://wendily.com/mcp",
"headers": {
"Authorization": "Bearer trips_sk_…"
}
}
}
}Authentication
Every request except ping needs a credential, and every credential acts for one user. It reaches the MCP server and the REST API and nothing else, and it can read and write that user's own trips and the trips shared with them, with the role they were given. It does not expire. The user can disconnect it at any time on the Connections page, and that takes effect on the next request.
OAuth
The right choice for any client that can open a browser. A request without a credential gets 401 with a pointer to the metadata:
WWW-Authenticate: Bearer realm="wendily", resource_metadata="https://wendily.com/.well-known/oauth-protected-resource/mcp"- Protected resource metadata (RFC 9728):
https://wendily.com/.well-known/oauth-protected-resource/mcp - Authorization server metadata (RFC 8414):
https://wendily.com/.well-known/oauth-authorization-server - Dynamic client registration (RFC 7591):
https://wendily.com/oauth/register, with no approval step - Authorization code flow with PKCE (
S256), as a public client - The one scope is
trips, and there are no refresh tokens
Redirect URIs must be https:// on chatgpt.com, claude.ai, claude.com, agent.meta.ai, muse.ai, or platform.muse.ai, or a loopback address (localhost, 127.0.0.1, [::1]). Custom URL schemes are refused, so a client that needs one should use a key. To support another app, write to team@wendily.com. The full walkthrough, with every request and error, is in auth.md.
Key
For clients without OAuth. The user adds a connection on the Connections page, which shows the key once, and you send it on every request as Authorization: Bearer trips_sk_….
Protocol
- Send each JSON-RPC 2.0 message as a
POSTto the server URL. The reply is one JSON response. Batches are refused. - The server is stateless: no session id and no server-to-client stream.
GETis not supported. - Methods:
initialize,ping,tools/list, andtools/call. Tools are the only capability. - Protocol versions: 2025-06-18, 2025-03-26.
curl https://wendily.com/mcp \
-H "Authorization: Bearer trips_sk_…" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Results and errors
A successful call returns the REST response as text and as structuredContent, so data is under data as it is in the API. An operation with nothing to return, such as a delete, returns the text Done.
- A rejected call (validation, permissions, an archived trip) is a result with
isError: trueand the textcode: message. The model can read it and correct itself.structuredContentholds the REST error. - A missing path id is also a result with
isError: true, naming the argument. - A missing, malformed, or revoked credential is an HTTP
401with theWWW-Authenticateheader above, never a tool result. Do not retry with the same token. Ask the user to reconnect. - An unknown tool is JSON-RPC error
-32602, and an unknown method is-32601.
Workflow
Clients show their model these instructions when they connect. If you drive the server yourself, follow them too. Calling start_trip before asking anything matters most: the user sees the trip appear in Wendily straight away, rather than nothing for the minutes the conversation takes.
Wendily stores trip itineraries. You write them here as you talk to the user about a trip. Workflow: 1. Before asking the user anything, check for a trip already in progress: if you know of one from an earlier conversation, or list_trips shows a trip that is still being planned, offer to continue it and use its id instead of starting over. 2. Otherwise call start_trip immediately, before your first reply. It creates a placeholder so the user can see in Wendily that you are working. Pass whatever you already know; nothing is required. Keep the returned id. 3. Ask the user a focused set of questions: destination, dates and flexibility, who is travelling and any practical needs, interests and pace, budget and preferences, anything already booked. 4. When you know the trip, call update_trip with the real title, destination, dates, and a cover image. Then add days with add_day and stops with add_item, including times, descriptions, locations, practical notes, travel between stops, and a photo for each place, activity, and stay when you can find a real one. 5. Keep the itinerary current as the plan changes, and after each meaningful update tell the user briefly what you changed. Rules: - Never invent bookings, prices, availability, reservation codes, or image URLs. Use direct links to real, publicly reachable photos, or leave the field out. - Dates are YYYY-MM-DD; times are HH:MM in the trip's local time. - Only the trip owner can invite people, and invitations must be accepted by the invitee before they see the trip.
Tools
There are 19 tools, each one REST operation. The arguments are the request body plus the path ids, and tools that change something are marked so that chat apps can ask the user first.tools/list returns the full JSON Schema for each.
Trips
List the trips the user can access, newest first. Use this before starting a new trip to see whether one is already being planned.
Arguments (4)
| kind | past | planned | `planned` for a trip being planned, `past` for one already taken. |
| archived | exclude | include | only | Archived trips are excluded by default. |
| limit | integer | |
| offset | integer |
Call this first, before asking the user anything. Creates a placeholder trip so the user sees their agent is working. Every argument is optional. Fill the trip in later with update_trip; do not create a second trip.
Arguments (9)
| title | string | |
| kind | past | planned | `planned` for a trip being planned, `past` for one already taken. |
| summary | string | A sentence or two about the trip. |
| destination | string | Where the trip goes, e.g. "Kyoto, Japan". |
| startDate | date | YYYY-MM-DD |
| endDate | date | YYYY-MM-DD |
| plannedFor | string | Who the trip is for, when not the user. |
| coverImageUrl | uri | A real, publicly reachable photo of the destination. Never invent URLs. |
| timeZone | string | IANA time zone, e.g. Europe/Athens. |
Create a trip whose details you already know. If you are about to gather them in conversation, use start_trip instead.
Arguments (9)
| titlerequired | string | |
| kindrequired | past | planned | `planned` for a trip being planned, `past` for one already taken. |
| summary | string | A sentence or two about the trip. |
| destination | string | Where the trip goes, e.g. "Kyoto, Japan". |
| startDate | date | YYYY-MM-DD |
| endDate | date | YYYY-MM-DD |
| plannedFor | string | Who the trip is for, when not the user. |
| coverImageUrl | uri | A real, publicly reachable photo of the destination. Never invent URLs. |
| timeZone | string | IANA time zone, e.g. Europe/Athens. |
Read one trip with all its days and items.
Arguments (1)
| tripIdrequired | uuid | The trip id. |
Change trip fields. Only the arguments you pass are changed. The first update to a placeholder turns it into an ordinary trip.
Arguments (10)
| tripIdrequired | uuid | The trip id. |
| title | string | |
| kind | past | planned | `planned` for a trip being planned, `past` for one already taken. |
| summary | string | A sentence or two about the trip. |
| destination | string | Where the trip goes, e.g. "Kyoto, Japan". |
| startDate | date | YYYY-MM-DD |
| endDate | date | YYYY-MM-DD |
| plannedFor | string | Who the trip is for, when not the user. |
| coverImageUrl | uri | A real, publicly reachable photo of the destination. Never invent URLs. |
| timeZone | string | IANA time zone, e.g. Europe/Athens. |
Archive a trip (owner only). It stays readable but cannot be changed until restored.
Arguments (1)
| tripIdrequired | uuid | The trip id. |
Restore an archived trip (owner only).
Arguments (1)
| tripIdrequired | uuid | The trip id. |
Days
Add a day to a trip. dayNumber is 1-based and unique within the trip.
Arguments (6)
| tripIdrequired | uuid | The trip id. |
| dayNumberrequired | integer | 1-based position within the trip. |
| date | date | YYYY-MM-DD; optional while dates are unsettled. |
| title | string | |
| summary | string | What the day is about, in a sentence. |
| notes | string | Practical advice: tickets, timing, what to bring. |
Change a day. Only the arguments you pass are changed.
Arguments (7)
| tripIdrequired | uuid | The trip id. |
| dayIdrequired | uuid | The day id, from get_trip or add_day. |
| dayNumber | integer | 1-based position within the trip. |
| date | date | YYYY-MM-DD; optional while dates are unsettled. |
| title | string | |
| summary | string | What the day is about, in a sentence. |
| notes | string | Practical advice: tickets, timing, what to bring. |
Delete a day and everything on it.
Arguments (2)
| tripIdrequired | uuid | The trip id. |
| dayIdrequired | uuid | The day id, from get_trip or add_day. |
Stops
Add a stop to a day: a place, activity, stay, transport leg, or note. Include times, a description, location, practical notes, travel from the previous stop, and a real photo URL when you have them.
Arguments (22)
| tripIdrequired | uuid | The trip id. |
| dayIdrequired | uuid | The day id, from get_trip or add_day. |
| kindrequired | place | activity | stay | transport | note | |
| titlerequired | string | |
| position | integer | 0-based order within the day; omit to append. |
| description | string | Traveller-facing context about the stop. |
| notes | string | Practical advice: tickets, timing, what to bring. |
| startTime | string | HH:MM, 24-hour, local to the trip. |
| endTime | string | HH:MM, 24-hour, local to the trip. |
| locationName | string | |
| locationAddress | string | |
| latitude | number | |
| longitude | number | |
| url | uri | The place or activity website. |
| imageUrl | uri | A real, publicly reachable photo of the stop. Never invent URLs. |
| travelMode | walk | transit | taxi | drive | bicycle | ferry | flight | other | How to reach this stop from the previous one. |
| travelDurationMinutes | integer | |
| travelDistanceMeters | integer | |
| provider | string | Airline, hotel, operator. |
| phone | string | |
| reservationCode | string | Only if the user gave it to you. |
| bookingUrl | uri |
Change a stop. Only the arguments you pass are changed.
Arguments (23)
| tripIdrequired | uuid | The trip id. |
| dayIdrequired | uuid | The day id, from get_trip or add_day. |
| itemIdrequired | uuid | The item id, from get_trip or add_item. |
| kind | place | activity | stay | transport | note | |
| title | string | |
| position | integer | 0-based order within the day; omit to append. |
| description | string | Traveller-facing context about the stop. |
| notes | string | Practical advice: tickets, timing, what to bring. |
| startTime | string | HH:MM, 24-hour, local to the trip. |
| endTime | string | HH:MM, 24-hour, local to the trip. |
| locationName | string | |
| locationAddress | string | |
| latitude | number | |
| longitude | number | |
| url | uri | The place or activity website. |
| imageUrl | uri | A real, publicly reachable photo of the stop. Never invent URLs. |
| travelMode | walk | transit | taxi | drive | bicycle | ferry | flight | other | How to reach this stop from the previous one. |
| travelDurationMinutes | integer | |
| travelDistanceMeters | integer | |
| provider | string | Airline, hotel, operator. |
| phone | string | |
| reservationCode | string | Only if the user gave it to you. |
| bookingUrl | uri |
Remove a stop from a day.
Arguments (3)
| tripIdrequired | uuid | The trip id. |
| dayIdrequired | uuid | The day id, from get_trip or add_day. |
| itemIdrequired | uuid | The item id, from get_trip or add_item. |
Sharing
List who has access to a trip and any pending invitations (owner sees pending).
Arguments (1)
| tripIdrequired | uuid | The trip id. |
Invite someone by email as an editor or viewer (owner only). They must accept in Wendily before they can see the trip.
Arguments (3)
| tripIdrequired | uuid | The trip id. |
| emailrequired | The person to invite. | |
| rolerequired | editor | viewer | Editors can change the itinerary; viewers can only read it. |
Remove the user’s own access to a trip they were invited to. Owners cannot leave.
Arguments (1)
| tripIdrequired | uuid | The trip id. |
Change a collaborator’s role (owner only).
Arguments (3)
| tripIdrequired | uuid | The trip id. |
| memberIdrequired | uuid | The member id, from list_members. |
| rolerequired | editor | viewer | Editors can change the itinerary; viewers can only read it. |
Remove a collaborator or cancel a pending invitation (owner only).
Arguments (2)
| tripIdrequired | uuid | The trip id. |
| memberIdrequired | uuid | The member id, from list_members. |
Send a pending invitation email again (owner only). Refused within ten minutes of the last send and after four sends.
Arguments (2)
| tripIdrequired | uuid | The trip id. |
| memberIdrequired | uuid | The member id, from list_members. |
Testing
There is no separate sandbox. Trips are private until shared, so test against the live server and archive test trips when you are done.
See also
- API reference: the same operations as REST.
- auth.md: every step of getting a credential.
- llms.txt: what an agent needs to integrate, as plain text.