Skip to Content

Tools

The Traveler.md MCP server exposes eight tools across two areas: traveler preferences and trips. Each tool requires a specific OAuth scope. See Authentication for the scope catalog.

Note what isn’t here: there is no search tool, no pricing tool, and no booking tool. This server is the traveler’s memory, so every tool below either hands you what they’ve already recorded or writes something new into it. Recommending and booking stay with your agent, which does both better once it has read the profile. See It’s memory, not a travel agent.

Scope summary

ToolRequired scopeWhat it allows
read_profileprofile.readRead your Traveler.md
create_profileprofile.createCreate your Traveler.md
update_profileprofile.updateUpdate your Traveler.md
list_tripstrip.listList your trips
read_triptrip.readRead a trip
create_triptrip.createCreate a trip
update_triptrip.updateUpdate a trip
archive_triptrip.updateArchive a trip

archive_trip reuses trip.update rather than introducing an archive scope of its own. Archiving is a trip mutation and is strictly reversible, so a client trusted to rewrite a trip is trusted to file one away — and a new scope would be missing from every already-issued token, silently removing the capability until each traveler re-consented.

Concepts shared across tools

A few conventions apply to every tool, so they’re described once here rather than repeated per tool:

  • Structured sections, not raw markdown. A Traveler.md or Trip.md is modeled as a map of section namearray of sentence strings (for example, flight_preferences: ["Prefers aisle seats", "Avoids red-eyes"]). Reads return this sections map; writes send it back in the same shape. Section names are fixed by the Traveler.md / Trip.md spec, and unknown names are rejected.
  • Section-level merge on writes. create_profile, update_profile, create_trip, and update_trip replace the sentence list of each section you include wholesale, and leave any section you omit untouched. This is not a whole-file replace: to leave a section alone, omit it.
  • Clearing a section is opt-in. Because a write replaces a section wholesale, sending one as an empty list erases it, and there is no delete tool and no server-side undo. update_profile and update_trip therefore reject an empty list unless the same call sets allow_clear_sections: true, and the validation error names every section the call would have emptied. The creates have no such flag: on a first write an empty section clears nothing.
  • Optimistic concurrency with version_hash. Reads return a version_hash (a 64-character SHA-256 hex string). Pass the last-seen value back as expected_version_hash on an update. If someone else wrote in the meantime, the call fails with 409 conflict and the error states the current hash. Prefer a fresh read over an immediate retry: sections are replaced wholesale, so re-sending the same body against 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_trip is the one exception to concurrency control: it changes no content, so there is no hash to conflict on and it takes no expected_version_hash.
  • include_markdown (optional, default false). Reads return the structured sections by default and omit the rendered markdown to keep responses small. Set include_markdown: true on any read or content write to also receive rendered_markdown. (archive_trip takes neither this nor idempotency_key — it writes no content.)
  • idempotency_key (optional). Any unique string (1–255 chars) on a create or update makes safe retries byte-identical for one hour.
  • Each section has a sentence cap. Most Traveler.md sections accept up to 50 sentences, some 20 or 30, and profile_overview — a short narrative summary, not a list — accepts 10. Overshooting a cap fails the whole write with a validation error naming the section and its limit; nothing partial is stored. Because a write replaces a section wholesale, the cap applies to what you send, not to the delta, so re-sending an existing section plus one new sentence has to fit as a whole. Send fewer, more concise sentences rather than splitting the write.

Traveler preferences

read_profile

Returns the authenticated traveler’s Traveler.md as structured sections.

FieldTypeNotes
sectionsarrayOptional. Section names to return, at least one. Omit for every section.
include_markdownbooleanOptional (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 / 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 call create_profile; it is not an error, and this read does not 404. Do not infer existence from sections being empty on its own.

sections on the way in is a projection, useful when a request turns on one or two of the twenty profile sections rather than all of them. It narrows the returned sections map only:

  • version_hash still 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 this when no profile exists yet, which is what a read_profile returning a null version_hash tells you. First write only: if a profile already exists it fails with 409 conflict and the message names the current hash, at which point switch to update_profile.

FieldTypeNotes
sectionsobjectRequired. Section name → array of sentences.
idempotency_keystringOptional. Unique string for safe retries.
include_markdownbooleanOptional (default false).

Required scope: profile.create

Returns: The new profile as sections plus its version_hash.

update_profile

Updates the traveler’s existing Traveler.md. Sections you include are replaced wholesale; sections you omit are left untouched.

FieldTypeNotes
sectionsobjectRequired. Section name → array of sentences.
expected_version_hashstringRequired. The version_hash from your last read_profile.
allow_clear_sectionsbooleanOptional (default false). Required to send any section as an empty list.
idempotency_keystringOptional. Unique string for safe retries.
include_markdownbooleanOptional (default false).

Required scope: profile.update

Returns: The full post-write profile — the same sections shape read_profile returns, so you don’t need a follow-up read — plus a new version_hash. Also includes changes, a per-section diff of what this write added, updated, or removed, when the pre-write state was available to compare against. Writes are versioned; a stale expected_version_hash fails with 409 conflict. See Debugging for history access.

Trips

list_trips

Lists the traveler’s trips, most-recently-updated first by default. Archived trips never appear.

FieldTypeNotes
statusstringOptional. One of Dreaming, Planning, Booking, Booked, Trip In Progress, Completed, Cancelled, On Hold.
querystringOptional. Case-insensitive substring searched across both the trip title and the trip’s section content.
starts_afterstringOptional. Inclusive ISO YYYY-MM-DD lower bound on start_date. Excludes trips with no start date.
starts_beforestringOptional. Inclusive ISO YYYY-MM-DD upper bound on start_date. Excludes trips with no start date.
sortstringOptional. recently_updated (default) or start_date for soonest departure first, with undated trips last.
limitintegerOptional (1–100, default 20).
cursorstringOptional. The opaque next_cursor from a previous page. Pass it through unchanged; don’t parse it.

Required scope: trip.list

Returns: A page of trips (title, status, dates, id) and a next_cursor when more results remain.

A trip matched by section content rather than by its title alone also carries matched_sections, naming the sections the phrase was found in. It carries section names only, never the matching text, and omits any section this client is not allowed to read; fetch the content with read_trip. So query: "ryokan" finds the trip whose accommodation mentions one even though its title does not.

“What is the traveler’s next trip” is therefore a single call: sort: "start_date", starts_after set to today, limit: 1.

Two paging rules:

  • Keep paging while next_cursor is present, even if the page’s items came back empty. A page is filtered after it is read, so an empty page alongside a next_cursor means “nothing on this page”, never “no results”. Only a response without a next_cursor ends the search.
  • A cursor is only valid for the ordering it was issued under. Changing sort 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.

FieldTypeNotes
trip_idstringRequired. The trip’s UUID.
include_markdownbooleanOptional (default false). Also return the markdown.

Required scope: trip.read

Returns: The trip envelope (title, status, dates, slug), its sections, and a version_hash. card_color is present only when the traveler picked a colour override for the trip card in their portal. 404 not_found if the trip doesn’t exist, isn’t visible to this token, or has been archived.

create_trip

Creates a new Trip.md for the authenticated traveler.

FieldTypeNotes
titlestringRequired. Short trip name, e.g. “Tokyo, March”.
statusstringRequired. One of the trip statuses listed under list_trips.
sectionsobjectRequired. Section name → array of sentences.
slugstringOptional. Derived from title if omitted.
start_datestringOptional. ISO YYYY-MM-DD.
end_datestringOptional. ISO YYYY-MM-DD.
idempotency_keystringOptional. Unique string for safe retries.
include_markdownbooleanOptional (default false).

Required scope: trip.create

Returns: The new trip with its stable UUID and a version_hash.

update_trip

Updates an existing Trip.md. Like update_profile, sections are merged section-by-section, and you can optionally patch envelope fields (title, status, slug, dates).

trip_id and expected_version_hash are the only required fields. sections is optional, so an envelope-only change — flipping a trip to Booked, correcting a date — doesn’t have to send a sections map at all.

FieldTypeNotes
trip_idstringRequired. The trip’s UUID.
expected_version_hashstringRequired. The version_hash from your last read_trip.
sectionsobjectOptional. Section name → array of sentences. Omit for an envelope-only update.
titlestringOptional. Rename the trip.
statusstringOptional. One of the trip statuses.
slugstringOptional.
start_datestringOptional. ISO YYYY-MM-DD.
end_datestringOptional. ISO YYYY-MM-DD.
allow_clear_sectionsbooleanOptional (default false). Required to send any section as an empty list.
idempotency_keystringOptional. Unique string for safe retries.
include_markdownbooleanOptional (default false).

Required scope: trip.update

Returns: The full post-write trip — envelope plus the same sections shape read_trip returns — and a new version_hash. Also includes changes, a per-section diff of what this write added, updated, or removed, when the pre-write state was available to compare against.

Every write response carries the new version_hash, so a chain of updates needs no re-read between them.

Two different situations both surface as 409 conflict, and the error message tells them apart: a stale expected_version_hash states the current hash inline, while a slug colliding with another of the traveler’s trips names no hash. Retrying with a hash cannot fix a slug collision; send a different slug or title instead.

archive_trip

Files a trip away. An archived trip leaves list_trips and can no longer be read or updated through this server, but it is not deleted — the traveler keeps it in the archive section of their traveler.md portal, where they alone can restore it or delete it for good.

Use this when the traveler wants a trip out of their active list because it’s over, abandoned, or clutter. Do not use it as a stand-in for deletion: there is no delete tool here, and archiving destroys nothing.

FieldTypeNotes
trip_idstringRequired. The trip’s UUID.

Required scope: trip.update

Returns: The trip_id and archived: true.

Notes:

  • No expected_version_hash, no idempotency_key. Archiving doesn’t touch trip content, so it can’t conflict with a concurrent edit.
  • Not idempotent. Calling it on a trip that is already archived returns 404 not_found, as does a trip that doesn’t exist or isn’t visible to this token. A second call is an error, not a no-op.
  • There is no unarchive tool. Archived trips are invisible to this server, so a client has no way to discover one to restore. If a traveler wants a trip back, direct them to the archive in their portal.

A note on agent behavior

Most agents will call read_profile once per session and cache the result. Writes should be deliberate and ideally confirmed with the traveler before being committed.