Tools
The Traveler.md MCP (Model Context Protocol) server exposes eight tools across two areas: traveler preferences and trips. Each tool requires a specific OAuth scope. See Authentication for the scope catalog.
The server has no search tool, no pricing tool, and no booking tool. It holds the traveler’s memory. Every tool below either returns what the traveler has already recorded or writes something new into it. Your agent does the recommending and booking after it reads the profile. See It’s memory, not a travel agent.
Scope summary
| Tool | Required scope | What it allows |
|---|---|---|
read_profile | profile.read | Read your Traveler.md |
create_profile | profile.create | Create your Traveler.md |
update_profile | profile.update | Update your Traveler.md |
list_trips | trip.list | List your trips |
read_trip | trip.read | Read a trip |
create_trip | trip.create | Create a trip |
update_trip | trip.update | Update a trip |
archive_trip | trip.update | Archive a trip |
archive_trip uses trip.update. It has no archive scope of its own. Archiving is a reversible trip change, so a client that can rewrite a trip can also file it away. A new scope would also be missing from every token already issued, so the capability would disappear until each traveler consented again.
Concepts shared across tools
These conventions apply to every tool:
-
Structured
sections. ATraveler.mdorTrip.mdis a map of section name to array of sentence strings, for exampleflight_preferences: ["Prefers aisle seats", "Avoids red-eyes"]. Reads return thissectionsmap. Writes send it back in the same shape. The Traveler.md and Trip.md specs fix the section names. The server rejects unknown names. -
Section-level merge on writes.
create_profile,update_profile,create_trip, andupdate_tripreplace the full sentence list of each section you include. They leave every section you omit unchanged. To leave a section alone, omit it. -
Clearing a section is opt-in. A section sent as an empty list is erased. There is no delete tool and no server-side undo. So
update_profileandupdate_tripreject an empty list unless the same call setsallow_clear_sections: true. The validation error names every section the call would have emptied. The create tools have no such flag, because a first write has nothing to clear. -
Optimistic concurrency with
version_hash. Reads return aversion_hash, a 64-character SHA-256 hex string. Every write response also returns the newversion_hash. Pass the last value you saw asexpected_version_hashon an update. If another writer changed the record in the meantime, the call fails withCONFLICTand the message states the current hash. Read again before you retry. Sections are replaced wholesale, so sending the same body with the new hash overwrites the other writer’s version of every section in it. Retry directly with the new hash only when your write cannot collide with theirs.archive_tripchanges no content, so it takes noexpected_version_hash. -
include_markdown(optional, defaultfalse). Reads return the structuredsectionsand omit the rendered markdown by default. Setinclude_markdown: trueon any read or content write to also receiverendered_markdown. -
idempotency_key(optional). Any unique string of 1 to 255 characters on a create or update. For one hour, the same key with the same body returns the byte-identical original response and does not write again. The server rejects a key that contains control characters or only whitespace. The same key with a different body fails withIDEMPOTENCY_MISMATCH. The same key sent while the first call is still running fails withIDEMPOTENCY_IN_PROGRESS.archive_triptakes no key. -
Sentence caps. Every
Traveler.mdsection accepts up to 100 sentences, exceptprofile_overview, which accepts 10. EveryTrip.mdsection accepts up to 100 sentences, exceptsummaryandpurpose, which accept 10 each. A write that exceeds a cap fails as a whole with a validation error that names the section and its limit. Nothing is stored. The cap applies to the full section you send, so an existing section plus one new sentence must fit inside it. -
Sensitive data is rejected. All four write tools check
sectionsand tripevents. A write that contains a payment card number, a passport or ID number, a known traveler number, or a door or access code fails withVALIDATION_ERROR, and the message names the section and the kind of data:Traveler.md never stores payment card numbers, passport or ID numbers, known traveler numbers, or door and access codes. Remove the number and resend; the sentence may name the passport country, card product, or trusted traveler program by name only.
Errors
A failed tool call returns a normal tool result with isError: true. It does not use an HTTP error status. structuredContent.error holds a code, a message, and sometimes details. code is one of these values:
| Code | Meaning |
|---|---|
CONFLICT | A stale expected_version_hash (details.currentHash holds the current hash), a create_profile for a profile that already exists, or a trip slug already in use (no hash in the message). |
NOT_FOUND | The trip does not exist, is not visible to this client, or is archived. |
VALIDATION_ERROR | The arguments failed the schema: an unknown section, a sentence cap, an empty section without allow_clear_sections, sensitive data, an invalid cursor, or an end_date before the start_date. |
IDEMPOTENCY_MISMATCH | The idempotency_key was already used with a different request body. |
IDEMPOTENCY_IN_PROGRESS | Another request with the same idempotency_key is still running. |
UNAUTHORIZED | The traveler revoked this connection or narrowed its scopes after consent, or a share grant was revoked or expired (details.reason). |
FORBIDDEN | The token never had the required scope (details.required_scope), or the traveler withheld a section from this client (details.disallowedSections). |
SERVICE_UNAVAILABLE | A temporary server problem. Retry after a short wait. |
INTERNAL | An unexpected server error. |
Some rejections happen at the HTTP transport, before any tool runs:
- 401 for a missing or invalid access token.
- 429 with the code
RATE_LIMITED, aretry-afterheader, andx-ratelimit-limit,x-ratelimit-remaining, andx-ratelimit-resetheaders. - 503 with the code
SERVICE_UNAVAILABLEandretry-after: 2, while a server instance shuts down or when the server cannot check rate limits.
See Debugging for what to do about each one.
Traveler preferences
read_profile
Returns the authenticated traveler’s Traveler.md as structured sections.
| Field | Type | Notes |
|---|---|---|
sections | array | Optional. Section names to return, at least one. Omit for every section. |
include_markdown | boolean | Optional (default false). Also return the markdown. |
Required scope: profile.read
Returns: sections (the parsed section map), a version_hash to pass back on a later update, and spec_version and renderer_version. rendered_markdown is included only when include_markdown is true.
If no profile exists yet, the call succeeds with a null version_hash and an empty sections map. That null is the signal to create the profile. It is not an error. Do not decide that no profile exists only because sections is empty.
The sections input narrows the returned sections map only. Use it when a request needs one or two of the twenty profile sections:
version_hashstill covers the whole document, so a filtered read is a safe basis for an update that names other sections.rendered_markdown, when requested, is always the complete document.- A section the traveler has withheld from this client stays absent whether or not you name it.
create_profile
Creates a Traveler.md for the authenticated traveler. Use it when read_profile returns a null version_hash. It is for the first write only. If a profile already exists, it fails with CONFLICT and the message names the current hash. Use update_profile with that hash instead.
| Field | Type | Notes |
|---|---|---|
sections | object | Required. Section name to array of sentences. |
idempotency_key | string | Optional. Unique string for safe retries. |
include_markdown | boolean | Optional (default false). |
Required scope: profile.create
Returns: The new profile as sections, its version_hash, and spec_version and renderer_version.
update_profile
Updates the traveler’s Traveler.md. Sections you include are replaced wholesale. Sections you omit are unchanged.
| Field | Type | Notes |
|---|---|---|
sections | object | Required. Section name to array of sentences. |
expected_version_hash | string or null | Required. The version_hash from your last read or write of the profile. Send null to create the profile when none exists yet. |
allow_clear_sections | boolean | Optional (default false). Required to send any section as an empty list. |
idempotency_key | string | Optional. Unique string for safe retries. |
include_markdown | boolean | Optional (default false). |
Required scope: profile.update. An expected_version_hash of null also requires profile.create.
Returns: The full profile after the write, in the same sections shape read_profile returns, so you do not need a follow-up read. It also returns a new version_hash. When the server has the earlier state to compare against, the response includes changes, a per-section diff of what this write added, updated, or removed. A wrong expected_version_hash fails with CONFLICT and writes nothing. See Debugging for write history.
Trips
list_trips
Lists the traveler’s trips, most recently updated first by default. Archived trips never appear.
| Field | Type | Notes |
|---|---|---|
status | string | Optional. One of Dreaming, Planning, Booking, Booked, Trip In Progress, Completed, Cancelled, On Hold. |
exclude_statuses | array | Optional. 1 to 8 statuses. Drops trips in any of them. |
query | string | Optional. Case-insensitive substring searched across the trip title and the trip’s section content. |
starts_after | string | Optional. Inclusive ISO YYYY-MM-DD lower bound on start_date. Excludes trips with no start date. |
starts_before | string | Optional. Inclusive ISO YYYY-MM-DD upper bound on start_date. Excludes trips with no start date. |
sort | string | Optional. recently_updated (default), or start_date for soonest departure first with undated trips last. |
limit | integer | Optional (1 to 100, default 20). |
cursor | string | Optional. The opaque next_cursor from a previous page. Pass it through unchanged. Do not parse it. |
Required scope: trip.list
Returns: items and next_cursor. Each item has trip_id, slug, title, status, start_date, end_date, and updated_at, plus card_color when the traveler picked a color for the trip card and matched_sections when query matched section content.
matched_sections names the sections where the phrase was found. It carries section names only, never the matching text. It omits any section this client is not allowed to read. Fetch the content with read_trip. So query: "ryokan" finds the trip whose accommodation section mentions one, even when its title does not.
“What is the traveler’s next trip” is a single call: sort: "start_date", starts_after set to today, exclude_statuses: ["Cancelled", "Completed"], and limit: 1. Always send exclude_statuses for a question about upcoming trips. A cancelled trip keeps its future dates, so without it the soonest trip can be one the traveler called off.
Paging rules:
- The filters run in the query.
next_cursoris set only when more rows exist. An emptyitemslist means there are no more results. - A cursor is valid only for the sort and filters it was issued under. Changing
sortor a filter mid-pagination fails with an invalid-cursor validation error. Start again with no cursor.
read_trip
Returns a single trip by ID as structured sections, with its dated bookings.
| Field | Type | Notes |
|---|---|---|
trip_id | string | Required. The trip’s UUID. |
include_markdown | boolean | Optional (default false). Also return the markdown. |
include_itinerary | boolean | Optional (default false). Also return the day-by-day summary. |
Required scope: trip.read
Returns:
- The trip envelope:
trip_id,slug,title,status,start_date, andend_date. sections,version_hash(can benull),spec_version, andrenderer_version.events, always: the trip’s dated bookings in timeline order, undated ones last. Each event carries itsevent_idand thesentenceit writes into the trip.itinerary, only wheninclude_itineraryistrue: the events grouped into days, with each day’s location. It is read-only. No tool accepts it.unstructured_fact_sentences, when present: sentences in a bookings section that have no event record. Resend each one througheventsto make it a booking.card_color, only when the traveler picked a color for the trip card.
The call fails with NOT_FOUND when the trip does not exist, is not visible to this client, or is archived.
create_trip
Creates a new Trip.md for the authenticated traveler. Create one as soon as the traveler mentions a place they want to go, even loosely, with status Dreaming. Call list_trips first so you extend an existing trip and do not create a duplicate.
| Field | Type | Notes |
|---|---|---|
title | string | Required. Short trip name, for example “Tokyo, March”. |
status | string | Required. One of the trip statuses listed under list_trips. |
sections | object | Optional (default {}). Section name to array of sentences. |
events | array | Optional. Dated bookings to add. See Trip events. |
slug | string | Optional. Derived from title if omitted. |
start_date | string or null | Optional. ISO YYYY-MM-DD. |
end_date | string or null | Optional. ISO YYYY-MM-DD. The server rejects an end_date before start_date. |
idempotency_key | string | Optional. Unique string for safe retries. |
include_markdown | boolean | Optional (default false). |
Required scope: trip.create
Returns: The new trip with its stable UUID, its sections, and a version_hash. The response can also include events and unstructured_fact_sentences.
update_trip
Updates an existing Trip.md. Like update_profile, it merges section by section. It can also change envelope fields (title, status, slug, dates) and add, edit, or remove events.
trip_id and expected_version_hash are the only required fields. An envelope-only change, such as setting a trip to Booked or correcting a date, does not need a sections map.
| Field | Type | Notes |
|---|---|---|
trip_id | string | Required. The trip’s UUID. |
expected_version_hash | string | Required. The version_hash from your last read or write of the trip. It covers the whole call. |
sections | object | Optional (default {}). Section name to array of sentences. |
events | array | Optional. Dated bookings to add, edit, or remove. See Trip events. |
title | string | Optional. Rename the trip. |
status | string | Optional. One of the trip statuses. |
slug | string | Optional. |
start_date | string or null | Optional. ISO YYYY-MM-DD. |
end_date | string or null | Optional. ISO YYYY-MM-DD. The server rejects an end_date before start_date. |
allow_clear_sections | boolean | Optional (default false). Required to send any section as an empty list. |
idempotency_key | string | Optional. Unique string for safe retries. |
include_markdown | boolean | Optional (default false). |
Required scope: trip.update
Returns: The full trip after the write, with the envelope and the same sections shape read_trip returns, and a new version_hash. The response can also include events and unstructured_fact_sentences. When the server has the earlier state to compare against, it includes changes, a per-section diff of what this write added, updated, or removed.
Two different situations both return CONFLICT, and the message tells them apart. A stale expected_version_hash states the current hash. A slug that another of the traveler’s trips already uses names no hash. A new hash cannot fix a slug collision. Send a different slug or title.
Trip events
Dated bookings go in events, not in sections. A booking is a flight, a stay, a train, a restaurant or activity reservation, a car hire, or anything else the traveler has arranged for a date. Narrative, preferences, and open questions stay in sections.
- Each item is
op: "upsert"orop: "remove". Anupsertwithout anevent_idcreates an event. Anupsertwith anevent_idedits that event. Aremovedeletes it. - A call accepts up to 25 events, applied atomically with any
sectionsin the same call. - The server writes a sentence for each event into the matching section of the trip. Do not also write the booking as prose.
startandendare objects, for example{"date": "2027-03-28", "time": "19:30", "tz": "Europe/Lisbon"}. Add atimeor anendonly when the traveler stated it.idempotency_keyis a field of the call. Never put it inside an event.
Events require the traveler to have given this client access to the trip itinerary. Without that access, a call that sends events fails with FORBIDDEN, details.disallowedSections contains trip:events, and nothing is written. A write from that client that does not send events succeeds, and the response carries itinerary_write_forbidden: true to show that no booking reached the itinerary.
archive_trip
Files a trip away. An archived trip leaves list_trips and can no longer be read or updated through this server. It is not deleted. The traveler keeps it in the archive section of their Traveler.md app, where only they can restore it or delete it.
Use this when the traveler wants a trip out of their active list because it is over, abandoned, or clutter. Ask the traveler before you call it. It is the only tool whose effect the traveler has to undo in the app. Do not use it to delete a trip. There is no delete tool, and archiving destroys nothing.
| Field | Type | Notes |
|---|---|---|
trip_id | string | Required. The trip’s UUID. |
Required scope: trip.update
Returns: The trip_id and archived: true.
Notes:
- No
expected_version_hash, noidempotency_key. Archiving does not change trip content, so it cannot conflict with a concurrent edit. - Not idempotent. A call on a trip that is already archived returns
NOT_FOUND. So does a trip that does not exist or is not visible to this client. - There is no unarchive tool. Archived trips are invisible to this server, so a client cannot find one to restore. Send the traveler to the archive in their app.
Prompts and resources
The server also publishes three MCP prompts. A client can offer them to the traveler as ready-made requests.
| Prompt | What it does |
|---|---|
plan_a_trip | Starts or continues planning a trip from the traveler’s saved preferences. Optional destination and when arguments. |
my_next_trip | Briefs the traveler on their soonest upcoming trip: dates, stays, what is booked, and what still needs a decision. |
remember_preference | Records a lasting travel preference on the traveler’s profile. Optional preference argument, in the traveler’s own words. |
Two resources hold the file specs:
traveler://spec/traveler-md/v1.0.0, theTraveler.mdspec.traveler://spec/trip-md/v1.0.0, theTrip.mdspec.
Agent behavior
Most agents call read_profile once per session and keep the result. Writes to the traveler’s own profile and trips proceed without a confirmation step. Ask the traveler before you archive a trip.