For developers

How to build on the Dyme MCP server when you are not one of the assistants it already knows.

What you are connecting to

Endpoint https://mcp.dyme.earth/api/mcp
Transport MCP Streamable HTTP: JSON-RPC over POST, answered as JSON or as an SSE stream according to the request's Accept. No GET stream (a GET answers 405)
Protocol versions 2025-11-25 back to 2024-11-05
Auth OAuth 2.1, PKCE, dynamic client registration. Every request needs a token. See Sign-in and authentication
Tools 46, listed in the tool reference and https://mcp.dyme.earth/docs/tools.json
Cards MCP Apps (io.modelcontextprotocol/ui). See Result cards
Health GET https://mcp.dyme.earth/health returns { ok, version, time }, no sign-in
Version serverInfo.version at initialize, version and fingerprint in tools.json. See Versions and changes

Getting your callback address approved

Dyme's sign-in returns only to callback addresses it has approved, so a new client can register but cannot finish sign-in until its address is added. This is deliberate: with open registration, the allowlist is what stops a stranger's client from collecting a signed-in traveler's code.

Email hello@dyme.earth with:

  1. Your product's name and website.
  2. The exact redirect_uri your client sends, https:// only, no query string.
  3. The user agent or clientInfo.name your client sends, so Dyme can recognize it.
  4. Whether your client draws MCP Apps cards.

Dyme adds the address and tells you. Once added, your client goes through the same flow as Claude: register, authorize, token.

Testing against the live server

There is no sandbox. Searches return live inventory and live prices, and the Book button opens a real checkout on travel.dyme.earth. Nothing is booked until a traveler pays there, so stopping at checkout is safe. Requests to the Dyme team (price match, call-back, change, cancellation) send a real email; tell hello@dyme.earth which account you test with so the team knows.

Calling tools from your own code

A tool call is an ordinary MCP tools/call. Each result has:

  • content[0].text: what the assistant's model reads, with a short hint on how to present it.
  • structuredContent: the same facts as JSON, for your own code or UI. Every tool declares an outputSchema.
  • _meta.ui.resourceUri on the tool (not the result) when the result has a card.

Follow-up calls (refine_hotels, get_hotel_rates, compare_prices, review_booking) take the trip_id a search returned. It is encrypted and tied to the signed-in traveler; a trip_id from another account is treated as unknown.

An argument the tool does not list is refused with isError: true and a sentence naming it, rather than ignored. A refine given a search-only field (dates, party, cabin) is refused with the name of the search tool to call instead.

Conventions

  • Airports and airlines are IATA codes. resolve_airport finds a code; an airline name in airlines is refused with the code it most likely means.
  • Dates are YYYY-MM-DD. Currency is an ISO code; send the one the traveler thinks in.
  • Prices are Dyme's figures. Do not add, convert or compare them in your own code and present the result as Dyme's.
  • personal: true declares a trip personal for a company traveler. Absent means the company's rules apply. There is no personal: false.

Ask

hello@dyme.earth. Include the trip_id or the time of the call when reporting a problem; logs hold no email addresses, so the id is how a call is found.