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
| 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 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. ATraveler.mdorTrip.mdis modeled as a map of section name → array of sentence strings (for example,flight_preferences: ["Prefers aisle seats", "Avoids red-eyes"]). Reads return thissectionsmap; 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, andupdate_tripreplace 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_profileandupdate_triptherefore reject an empty list unless the same call setsallow_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 aversion_hash(a 64-character SHA-256 hex string). Pass the last-seen value back asexpected_version_hashon an update. If someone else wrote in the meantime, the call fails with409 conflictand 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_tripis the one exception to concurrency control: it changes no content, so there is no hash to conflict on and it takes noexpected_version_hash. include_markdown(optional, defaultfalse). Reads return the structuredsectionsby default and omit the rendered markdown to keep responses small. Setinclude_markdown: trueon any read or content write to also receiverendered_markdown. (archive_triptakes neither this noridempotency_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.mdsections accept up to 50 sentences, some 20 or 30, andprofile_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.
| 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 / 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_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 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.
| Field | Type | Notes |
|---|---|---|
sections | object | Required. Section name → 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 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.
| Field | Type | Notes |
|---|---|---|
sections | object | Required. Section name → array of sentences. |
expected_version_hash | string | Required. The version_hash from your last read_profile. |
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
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.
| Field | Type | Notes |
|---|---|---|
status | string | Optional. One of Dreaming, Planning, Booking, Booked, Trip In Progress, Completed, Cancelled, On Hold. |
query | string | Optional. Case-insensitive substring searched across both 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–100, default 20). |
cursor | string | Optional. 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_cursoris present, even if the page’s items came back empty. A page is filtered after it is read, so an empty page alongside anext_cursormeans “nothing on this page”, never “no results”. Only a response without anext_cursorends the search. - A cursor is only valid for the ordering it was issued under. Changing
sortmid-pagination fails with an invalid-cursor validation error; start again with no cursor.
read_trip
Returns a single trip by ID as structured sections.
| Field | Type | Notes |
|---|---|---|
trip_id | string | Required. The trip’s UUID. |
include_markdown | boolean | Optional (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.
| Field | Type | Notes |
|---|---|---|
title | string | Required. Short trip name, e.g. “Tokyo, March”. |
status | string | Required. One of the trip statuses listed under list_trips. |
sections | object | Required. Section name → array of sentences. |
slug | string | Optional. Derived from title if omitted. |
start_date | string | Optional. ISO YYYY-MM-DD. |
end_date | string | Optional. ISO YYYY-MM-DD. |
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 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.
| Field | Type | Notes |
|---|---|---|
trip_id | string | Required. The trip’s UUID. |
expected_version_hash | string | Required. The version_hash from your last read_trip. |
sections | object | Optional. Section name → array of sentences. Omit for an envelope-only update. |
title | string | Optional. Rename the trip. |
status | string | Optional. One of the trip statuses. |
slug | string | Optional. |
start_date | string | Optional. ISO YYYY-MM-DD. |
end_date | string | Optional. ISO YYYY-MM-DD. |
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 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.
| 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 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.