Туту.ру MCP: поиск билетов

Туту.ру MCP: поиск билетов

Удалённый MCP-сервер Туту: AI-ассистент ищет билеты и маршруты прямо из диалога. Подключение без авторизации.

Туту MCP: поиск билетов через ИИ-ассистента

Туту MCP подключает Claude, Cursor и другие ИИ-ассистенты к поиску билетов Туту. Пишете в чате «хочу в Казань в пятницу вечером», ассистент сам идёт в поиск и приносит варианты. Листать расписания не нужно.

Сервер удалённый, на компьютер ничего ставить не придётся. Ни аккаунта, ни API-ключа: хватает одного адреса.

Что умеет Туту MCP

Ассистент получает от сервера инструменты поиска Туту, в том числе мультитранспортный: один запрос по маршруту и дате, ответ по разным видам транспорта. Полный список инструментов клиент запрашивает сам при подключении, так что новые возможности сервера появляются у вас без обновлений и правок конфига.

Что можно спросить:

  • «Найди билеты Москва - Санкт-Петербург на ближайшую субботу»
  • «Что есть из Москвы в Нижний Новгород в воскресенье утром?»
  • «Как добраться до Казани в пятницу вечером? Покажи, что вернул поиск»

Какой инструмент вызвать, ассистент решает сам и выводит результат прямо в диалог.

Как подключить Туту MCP

Параметры подключения:

Инструменты были проиндексированы:
create_checkout_linkтолько чтениеидемпотентныйвнешний мир

The single 'proceed to checkout' handle for a previously found offer. Call it as soon as the user commits to one option, however they word it — «беру», «самый быстрый из списка», «оформи», «забронируй», «дай ссылку» — and put the returned link in the same reply. It needs NO seat choice and NO passenger data in its default mode — for AVIA that default link already IS the cart for the chosen flight, while for rail/bus/hotels it opens the seat/room selection page and only a user-confirmed seat/room turns it into a cart link. Pass the fields from the offer's checkout_ref object (emitted by every search_hotels / search_avia / search_rail / search_bus / search_etrain / search_multitransport result); the tool dispatches by product_type (or the transport alias). Returns {checkout_url, kind, ...}. Every branch is a PURE URL builder — no network calls, no server-side cart, idempotent. The link is what the USER opens; their browser creates the cart in their own session. (We do NOT mint carts server-side: such a cart is session-bound and 404s for the user.) The returned checkout_url is an OPAQUE string. Hand it to the user byte-for-byte as this tool returned it: never rebuild it from parts, re-encode it, drop or reorder query parameters, or swap in ids you saw elsewhere in the payload. The response deliberately carries SEVERAL id spaces side by side — e.g. bus checkout_ref holds both departure_id (the route id the link needs) and departure_geo_city_id (the dictionary city id, the same value as meta.from.geo_id) — and this tool already picked the right one per product. An edited link usually still loads, so 'it opens' does NOT prove it leads to the offer the user chose: the usual outcome is another offer or a bare search page. Per product (inputs from checkout_ref): - avia: returns BOTH checkout_url (a Tutu mtp-deeplink that 302-redirects to the ticket's purchase page and mints the cart there — works in a cold browser; the cart belongs to whoever OPENS the link. If the offer is no longer available it lands on the avia search for this route+date instead) and search_results_url. Present it as 'go book', not 'your cart is ready'. Inputs: search_results_url, offer_hash, departure_geo_city_id, arrival_geo_city_id, departure_at, service_class, plus passengers_full / passengers_child / passengers_infant (the searched party — forward them so the cart requests the SAME passengers; omit and the cart opens with one adult, so a multi-passenger total won't match — have the user confirm the count in the cart). Round-trip: also forward is_round_trip + return_departure_at from checkout_ref — a direct round-trip deeplink then carries BOTH legs in one cart. Connecting round-trips return search_redirect: explicit/avia currently supports one flight per direction. SEPARATE TICKETS (is_multi_pnr on a one-way offer): forward checkout_ref.checkout_parts too — this tool cannot put such a trip in ONE link, so it returns kind="deeplink_parts" with one purchase link per ticket in checkout_parts[] (ticket/of, from, to, flights, checkout_url) and leaves the top-level checkout_url on the SEARCH page. Give the user every link, in order, and say each is its own cart and its own payment. For a non-cheapest fare override offer_hash + service_class from the chosen variants[i] (keep forwarding is_round_trip + return_departure_at). For avia, while the user compares flights, show ONLY search_results_url and do NOT call create_checkout_link prematurely. Once they choose an exact flight and the tool returns kind="deeplink", show checkout_url as the primary purchase link together with search_results_url as the fallback for browsing. When it returns kind="search_redirect", show search_results_url as the search page and explain fallback_note; do not call it a purchase deeplink. - rail: an explicit/train deeplink. TWO modes. (1) Seat page (default): inputs departure_geo_point_id, arrival_geo_point_id, train_number, departure_at (ISO) — the link opens the train's order page where the user picks seats. (2) Straight-to-cart (kind="checkout_deeplink"): additionally pass the seat choice — car_number + seat_numbers (from get_rail_seatmap: cars[].car_number, seats[].number; one seat per passenger) plus offer_hash + segment_hash from checkout_ref — and the link mints the cart with those exact seats pre-selected (works in a cold browser; the cart belongs to whoever OPENS the link). Optional: fare_type (the chosen fares[].fare_type from the seatmap, e.g. REFUNDABLE), gender_type (for gender-policy coupes: MALE/FEMALE — ask the user; omit for regular cars), and search_id/result_id/card_id metadata from checkout_ref. Use straight-to-cart ONLY after the user explicitly confirmed specific seats; the cart skips the seat wizard, so a silently wrong seat would go unnoticed. Falls back to the tutu.ru/poezda/order page (needs departure_city_id, arrival_city_id, departure_station_code, arrival_station_code) if the deeplink can't be built. - bus: an explicit/bus deeplink. TWO modes, same idea. (1) Seat page (default): offer_hash, departure_geo_city_id, arrival_geo_city_id, departure_at (ISO). (2) Straight-to-cart: additionally seat_numbers (from get_offer_details's seat_selection.available_seat_ids; one per passenger) + search_id + departure_id/arrival_id (all REQUIRED here — pass them through from checkout_ref). Falls back to the bus.tutu.ru/seats page (departure_id, arrival_id, city_from, city_to) otherwise. - etrain: tutu.ru schedule page from departure_etrain_id, arrival_etrain_id, departure_at. - hotels: an explicit/hotel deeplink. TWO modes, same idea as rail/bus. (1) Page (default, kind="deeplink"): hotel_alias + check_in + check_out + adults (+ children_ages, search_id) opens the hotel's page with dates/guests pre-filled for the user to pick a room. (2) Straight-to-cart (kind="checkout_deeplink"): also pass offer_pack_hash — a ROOM rate's offerpack_hash from get_offer_details (after the user picked a room) — and the link mints the cart for that pack, landing on the hotel page if the pack expired (fallback_to_details). NB the listing best_offer.offerpack_hash does NOT mint a cart — only a room-rate hash does. Page-mode fields ride in checkout_ref. Falls back to the pre-filled fallback_url page (kind="hotel_page") if the ref has no hotel_alias. kind ∈ deeplink / checkout_deeplink (rail/bus with seats; hotels with a pack) / deeplink_parts (avia separate tickets — links in checkout_parts[], top-level checkout_url is the search page) / search_redirect (avia) / order_url / seats_url (rail/bus fallback) / hotel_page (hotels fallback).

Create a checkout link for a selected offer (Tutu.ru)

Параметры
  • adultsinteger | null

    For hotels: number of adults (checkout_ref.adults).

  • arrival_avia_idstring | integer | null

    Accepted from checkout_ref (avia orders-API city id, used by register_checkout_passengers); ignored here — safe to forward.

  • arrival_city_idinteger | null

    For rail: destination city id (arrival_st).

  • arrival_etrain_idinteger | null

    For etrain: destination station id for tutu.ru commuter schedule pages (st2).

  • arrival_geo_city_idinteger | null

    For avia: arrival city id (legs[-1].segments[-1].to.city_id, also in checkout_ref.arrival_geo_city_id). Required for the mtp-deeplink purchase URL.

  • arrival_geo_point_idinteger | null

    For rail: destination segment geo-point id (checkout_ref.arrival_geo_point_id). Required for the rail explicit/train deeplink.

  • arrival_idinteger | null

    For bus: destination route id for search[to] on bus.tutu.ru/seats. Prefer this field from checkout_ref.

  • arrival_station_codestring | null

    For rail: destination station code (arr_st). For etrain this is retained as a compatibility/debug field.

  • arrival_stop_idinteger | null

    For bus: display/debug destination stop geo-point id. Older checkout_ref objects used this as search[to]; new objects should also pass arrival_id.

  • arrival_stop_namestring | null

    For bus: display name for the destination stop. Accepted for pass-through compatibility; URL building uses arrival_id when present.

  • card_idstring | null

    For rail straight-to-cart: checkout_ref.card_id metadata (optional).

  • car_numberstring | integer | null

    For rail straight-to-cart: the chosen car (cars[].car_number from get_rail_seatmap).

  • check_instring | null

    For hotels: check-in date YYYY-MM-DD (checkout_ref.check_in).

  • check_outstring | null

    For hotels: check-out date YYYY-MM-DD (checkout_ref.check_out).

  • checkout_partsobject[] | null

    For avia SEPARATE-TICKET offers (is_multi_pnr): forward checkout_ref.checkout_parts verbatim. It carries one entry per TICKET (departure_geo_city_id, arrival_geo_city_id, departure_at, plus from/to/flights labels) and the tool returns one purchase link per ticket (kind="deeplink_parts"). THIS tool cannot cover such an itinerary with one link — omit this and it can only hand back the search page. (That is this link hand-off's limit, not the trip's: where register_checkout_passengers is registered, the same checkout_ref fills ONE cart for the whole itinerary.) Present on multi-PNR offers only; other offers and other transports ignore it.

  • children_agesinteger[] | null

    For hotels: child ages (checkout_ref.children_ages), if any.

  • city_fromstring | null

    For bus: origin city name.

  • city_tostring | null

    For bus: destination city name.

  • departure_atstring | null

    ISO-8601 departure datetime from the offer's first segment (e.g. 2026-04-30T13:30:00+03:00). Used by rail (date), etrain (date), and bus (search[on] + trip[start_time]).

  • departure_avia_idstring | integer | null

    Accepted from checkout_ref (avia orders-API city id, used by register_checkout_passengers); ignored here — safe to forward.

  • departure_city_idinteger | null

    For rail: origin city id (departure_st).

  • departure_etrain_idinteger | null

    For etrain: origin station id for tutu.ru commuter schedule pages (st1).

  • departure_geo_city_idinteger | null

    For avia: departure city id (legs[0].segments[0].from.city_id, also in checkout_ref.departure_geo_city_id). Required for the mtp-deeplink purchase URL.

  • departure_geo_point_idinteger | null

    For rail: origin segment geo-point id (checkout_ref.departure_geo_point_id = legs[0].segments[0].from.geo_point_id). Required for the rail explicit/train deeplink.

  • departure_idinteger | null

    For bus: origin route id for search[from] on bus.tutu.ru/seats. Prefer this field from checkout_ref.

  • departure_station_codestring | null

    For rail: origin station code (dep_st, e.g. 2000001). For etrain this is retained as a compatibility/debug field.

  • departure_stop_idinteger | null

    For bus: display/debug origin stop geo-point id. Older checkout_ref objects used this as search[from]; new objects should also pass departure_id.

  • departure_stop_namestring | null

    For bus: display name for the origin stop. Accepted for pass-through compatibility; URL building uses departure_id when present.

  • fallback_urlstring | null

    For hotels: the pre-filled hotel page (checkout_ref.fallback_url / best_offer.checkout_url) — this is what hotels checkout returns.

  • fare_typestring | integer | null

    For rail straight-to-cart: the chosen fare type — pass the seatmap fares[].fare_type string (REFUNDABLE→1, NON_REFUNDABLE→2) or Tutu's integer code directly. ALWAYS pass it when the user picked a fare: omitted, the cart opens on the default (refundable) fare, which is pricier than a chosen non-refundable one. Unknown labels are omitted from the URL (tell the user to confirm the fare in the cart).

  • gender_typestring | integer | null

    For rail straight-to-cart, gender-policy coupes only: which gender the compartment is sold as — MALE/FEMALE (ask the user; MIXED/NO_GENDER and ints 0..3 also accepted). Omit for regular cars.

  • hotel_aliasstring | null

    For hotels: the hotel's /h_<alias>/ slug (checkout_ref.hotel_alias, also best_offer/row alias from search_hotels or hotel.alias from get_offer_details). Required to build the explicit/hotel deeplink; without it the branch degrades to the pre-filled hotel page (fallback_url).

  • hotel_geo_idstring | integer | null

    For hotels: the hotel's geo id (checkout_ref.hotel_geo_id). Display/debug; the page URL comes from fallback_url.

  • is_round_tripboolean | null

    For avia: pass checkout_ref.is_round_trip. A direct round-trip offer builds a TWO-leg deeplink (both legs in one cart) — the tool joins the per-leg hashes and adds the return departure from return_departure_at. Connecting round-trips return the search page because explicit/avia currently supports one flight per direction. Forward return_departure_at from checkout_ref alongside this flag; without it the tool falls back to the search page. Other transports ignore this field.

  • offer_hashstring | object | null

    For avia: the stringified JSON from offer.variants[i].offer_hash; the deeplink hashes are extracted from it to build the purchase URL. A direct round-trip offer_hash carries both legs — the tool joins them and needs return_departure_at too. For bus: the offer_hash string used as trip[hash] in the checkout URL. If your host auto-parses the JSON back into a dict, pass it as a dict — we'll normalise it.

  • offer_pack_hashstring | null

    For hotels straight-to-cart: a ROOM rate's rooms[].rates[].offerpack_hash from get_offer_details — pass it once the user picked a specific room and the deeplink mints the cart for that pack (checkout=true), falling back to the hotel page if the pack expired (fallback_to_details=true). NB the listing best_offer.offerpack_hash does NOT mint a cart (the redirector falls back to the hotel page); only a room-rate hash from get_offer_details does. Omit to open the hotel page for the user to pick a room.

  • passengersinteger

    For the bus fallback seats_url: number of passengers to prefill. Rail deeplinks/order URLs do not preselect passengers or seats.

  • passengers_adultinteger | null

    Accepted from a bus checkout_ref (the adult share of the searched party, checked by register_checkout_passengers); ignored here — safe to forward.

  • passengers_childinteger | null

    For avia: number of children (2–11 yrs) (checkout_ref.passengers_child). See passengers_full.

  • passengers_fullinteger | null

    For avia: number of adults the search was for (checkout_ref.passengers_full). Pass it so the deeplink requests the SAME party you quoted — omit and Tutu defaults the cart to one adult, so a multi-passenger total won't match. Forward passengers_full/child/infant from checkout_ref together.

  • passengers_infantinteger | null

    For avia: number of infants (<2 yrs, lap) (checkout_ref.passengers_infant). See passengers_full.

  • product_typeenum | null

    Product type of the offer. railway and rail are accepted as synonyms. Optional when passing transport from checkout_ref.

  • result_idstring | null

    For rail straight-to-cart: checkout_ref.result_id metadata (optional).

  • return_departure_atstring | null

    For round-trip avia: ISO-8601 departure of the RETURN leg's first segment (checkout_ref.return_departure_at = legs[1].segments[0].departure_at, e.g. 2026-07-19T10:00:00+03:00). Required to build a two-leg direct round-trip deeplink; omit for one-way.

  • search_idstring | null

    checkout_ref.search_id — the searchId of the search the offer came from. REQUIRED for the bus straight-to-cart mode; optional metadata for rail.

  • search_results_urlstring | null

    For avia: the avia.tutu.ru search-results URL emitted in checkout_ref.search_results_url. Always returned alongside the deeplink so the user can fall back to browsing the listing page.

  • seat_countinteger | null

    For bus straight-to-cart: number of passengers. Defaults to the number of seat_numbers passed.

  • seat_numbersstring | integer[] | null

    Straight-to-cart seat choice, ONE seat per passenger. For rail: seats[].number values from get_rail_seatmap (all in the same car_number). For bus: ids from get_offer_details seat_selection.available_seat_ids. Pass ONLY seats the user explicitly confirmed — the link skips the seat wizard.

  • segment_hashstring | null

    For rail straight-to-cart: the offer's segment hash (checkout_ref.segment_hash). Required together with offer_hash, car_number and seat_numbers to mint the cart.

  • service_classstring | integer | null

    For avia: cabin class of the chosen variant. Three input shapes accepted: Tutu's upstream code (ECONOMIC/PREMIUM_ECONOMY/BUSINESS/FIRST — preferred, lives on each variant as variants[i].service_class and on the cheapest in checkout_ref.service_class), IATA letter (Y/S/C/F — what search_avia was called with), or the raw deeplink integer (1=Economy, 2=PremiumEconomy, 3=Business, 4=First). When overriding offer_hash for a non-cheapest fare family, also override this from the same variant — and keep passing is_round_trip from checkout_ref (a fare swap doesn't change the offer's round-trip-ness).

  • train_numberstring | null

    For rail: the Express-3 train number such as 022А. Copy it from offer.checkout_ref.train_number — do NOT read segments[].voyage_no, which is the passenger display number and differs from the bookable number for through-trains (e.g. displays 060*Г, books 059Г).

  • transportenum | null

    Backward-compatible alias for product_type; matches the transport key emitted in checkout_ref.

fetch_resourceтолько чтениеидемпотентный

Read a tutu:// server resource and return its content. Use this when your MCP client doesn't auto-surface server resources in the prompt. Valid URIs: tutu://help/overview (full agent guide — start here), tutu://geo (city/point ids), tutu://status (server + upstream health), tutu://special-offers (experimental inspiration only). Returns {uri, mime_type, content}.

Fetch a tutu:// MCP resource by URI

Параметры
  • urienum | stringобязательный

    Resource URI. Start with tutu://help/overview for the agent-facing reference.

get_avia_instructionsтолько чтениеидемпотентный

Detailed avia playbook: airport disambiguation, airport-scoped search (origin/destination by airport name or IATA code), deeplink vs search-results checkout, fare-family override, baggage/refund grounding. Read before working with search_avia results.

Avia search & checkout playbook (Tutu.ru)

Параметры

Без параметров.

get_bus_instructionsтолько чтениеидемпотентный

Detailed bus playbook: passengers (adults + children, whole-party pricing, composition in checkout_ref), stop presentation, get_offer_details (amenities/refund/luggage/seat_selection), checkout (seat page vs straight-to-cart with chosen seats) and grounding. Read before working with search_bus results.

Bus search & checkout playbook (Tutu.ru)

Параметры

Без параметров.

get_etrain_instructionsтолько чтениеидемпотентный

Detailed etrain (commuter) playbook: per-seat price basis, vehicle_meta consist type, get_offer_details reformat, checkout and grounding. Read before working with search_etrain results.

Commuter-train search & checkout playbook (Tutu.ru)

Параметры

Без параметров.

get_hotels_instructionsтолько чтениеидемпотентный

Detailed hotels playbook: hotels-vs-transport geo_id pitfall, clarifying questions, best_offer vs get_offer_details, view/bed/breakfast grounding. Read before working with search_hotels results.

Hotels search & details playbook (Tutu.ru)

Параметры

Без параметров.

get_multitransport_instructionsтолько чтениеидемпотентный

Detailed multitransport playbook: nested variants[], per-mode price basis (compare on price_party, not price), optimize_for, per-mode soft-fail, and how checkout defers to the per-mode playbooks. Read before working with search_multitransport results.

Multitransport compare playbook (Tutu.ru)

Параметры

Без параметров.

get_offer_detailsтолько чтениеидемпотентныйвнешний мир

Fetch details for a single offer. Defaults to view='compact': for hotels it caps photos, omits the per-rate cancellation_policy text (the free_cancellation / free_cancellation_until flags stay), omits room amenity_groups (flat room_amenities stays) and omits review TEXTS — the review_summary aggregate stays. Use view='rules' for the exact cancellation ladder, view='reviews' + review_limit for a feedback-only card (review texts with pagination; rooms collapse to stubs), or view='full' for everything. Transport details ignore view. product_type=hotels takes the hotel id under any of offer_id / hotel_id / hotel_geo_id — three names for the SAME value, so copy the field off the search_hotels row and pass exactly one (two different ids is an error: it means two rows got mixed). It returns hotel info (name, address, photos, hotel-level amenities, rules, review_summary) plus rooms[] — every room category Tutu sells, each with: room_name, room_description, photos, bed_summary + bed_type (single/twin/double), view (sea/water/mountain/park/city/river/lake/yard/other when Tutu wrote it into the room name), room_size_sqm (room area in m² when Tutu exposes it, often a minimum 'от N'; usually null here — the detail upstream rarely carries area, the listing best_offer.room_size_sqm is the more reliable source — so say 'площадь не указана', don't guess) and max_occupancy (None when Tutu doesn't provide it), amenity_groups grouped by Спальня/Ванная/Техника, flat room_amenities, structured room_property_items, price_from (THIS room's cheapest rate, same {amount, currency, price_basis} shape as a rate price — quote a room's price from here so name, size, view and price all come off ONE object; null when no rate is priced), and rates[]. Each rate carries price (the WHOLE-STAY total for the requested dates and guests, tagged price_basis="stay_total" — render as-is, NEVER multiply by nights; the top-level stay {check_in, check_out, nights} block carries the night count), meal {included, name, type}, breakfast_included, refundable, free_cancellation + free_cancellation_until, full cancellation_policy[], pay_at_hotel, pay_online, raw highlights[], and offerpack_hash. When rooms is empty AND notices[] carries kind=no_room_offers_for_dates, this is Tutu's encoding of 'provider has no inventory on these dates' (often transient — same hotel+dates can return rooms within the hour). Surface notices[].agent_hint to the user as the explanation, do not treat it as a tool error; the hotel's static info (name, address, photos, amenities, rating, reviews) is still populated. product_type=rail uses offer.details_ref from search_rail to load service_classes[] — one card per class (купе/плацкарт/СВ code in service_class, description, services, amenities[] with codes + Russian labels, per-class review_summary, photos) — plus lean fare rows in variants[] ({offer_hash, price, fare_type REFUNDABLE/NON_REFUNDABLE, service_class, class_index, car_type, car_number, seat_type, seats_count}; class_index points into service_classes[] — join by it, not by the display name), cars[] (per-car availability — sum cars[].seats_count for the train's free-seat total; do NOT sum variants[].seats_count, fare rows share seat groups so summing them double-counts), train_vehicle (name, is_premium / is_firm / is_double_decker, has_photo, has_tour_3d) and a ticket block (format=eticket, boarding=by_passport). product_type=bus uses offer.details_ref from search_bus to load structured amenities[] (Wi-Fi / power_outlet / air_conditioning / toilet flags), bus_category (model + service class), ticket (eticket flag + documents_accepted), refund.blocks[] and luggage.blocks[] with structured rule text, plus seat_selection (required, free_count, available_seat_ids, has_scheme). For bus straight-to-cart, pick seat_numbers only from available_seat_ids; if that list is empty or missing but free_count is positive, say that exact-seat preselection is unavailable in this response, NOT that there are no seats. product_type=etrain returns the search-envelope fallback with summary.vehicle_type (Ласточка vs Дальний with description) and a ticket block. product_type=avia can format a selected search offer passed as details_ref; the richer avia order endpoint is not called here because it creates a draft order. For amenity-code localization see tutu://amenities/dictionary.

Get offer details (Tutu.ru)

Параметры
  • adultsinteger

    Number of adult guests.

  • check_instring | null

    Required for hotels. YYYY-MM-DD.

  • check_outstring | null

    Required for hotels. YYYY-MM-DD.

  • children_agesinteger[] | null

    Ages of accompanying children.

  • details_refobject | null

    For rail and bus: pass offer.details_ref from the corresponding search result verbatim. For avia/etrain: pass the selected compact offer object if you want a presentation-ready reformat without side effects.

  • hotel_geo_idstring | null

    Alias for offer_id, named as search_hotels and create_checkout_link spell it.

  • hotel_idstring | null

    Alias for offer_id, named as search_hotels returns it. Pass one of the three, not several.

  • offer_idstring | null

    REQUIRED for hotels (or its aliases hotel_id / hotel_geo_id — same value, pass any ONE): Tutu hotel id, i.e. the hotel_id / hotel_geo_id field of the search_hotels row you are detailing, the numeric id used in details URLs; tutu_offer_id UUID is not accepted by Tutu details endpoints. Optional for transport — details_ref alone is enough.

  • product_typeenumобязательный

    Type of offer to inspect. Only hotels has text reviews; rail/bus have read-only detail endpoints.

  • review_limitinteger

    For hotels: number of review texts to return in view='reviews' / view='full'. Default 5, max 50; page with review_offset. Ignored by compact/rules — those views carry only review_summary and skip the review fetch entirely.

  • review_offsetinteger

    For hotels: review pagination offset. Use hotel.reviews.pagination.has_more to fetch next page.

  • review_orderenum

    For hotels: review sort order.

  • review_sortenum

    For hotels: sort review texts by date or rating.

  • review_topicsstring | null

    For hotels: optional topic filter from hotel.reviews.topics. Empty/None = all topics.

  • viewenum

    Response detail level. Hotel detail scope: compact (default) / rules / reviews / full. compact is the lean decision card: room photos capped, per-rate cancellation_policy text dropped (the decision facts stay in free_cancellation + free_cancellation_until), room amenity_groups dropped (flat room_amenities kept), hotel photos capped, hotel amenity_groups slimmed to names, and review TEXTS omitted — the review_summary aggregate (rating + aspects) stays; for guest quotes call view='reviews'. rules = compact + the full cancellation ladder. reviews is a feedback-only card: hotel identity + review_summary + reviews[] texts (set review_limit), rooms collapse to name + cheapest-price stubs, no photos/amenities/policy. full returns every block (photo arrays still bounded to a small gallery). Transport details ignore view.

get_rail_instructionsтолько чтениеидемпотентный

Detailed rail playbook: per-seat price basis (price_party for a party of 2+), get_rail_seatmap workflow (pagination, seat types, group_index join, per-group fare variants, focused task= queries incl. together for «места рядом», no-layout fallback), gender-coupe rules, get_offer_details and checkout (seat page vs straight-to-cart with chosen seats). Read before working with search_rail results.

Rail search, seat map & checkout playbook (Tutu.ru)

Параметры

Без параметров.

get_rail_seatmapтолько чтениеидемпотентныйвнешний мир

Read-only per-car seat layout for a selected rail offer. Authoritative next step after search_rail for ANY question about exact seats, car layout, female/male/mixed compartments, lower/upper or nearby seats, or WC distance: if a rail offer has details_ref, CALL this — never tell the user the seat map is unavailable without calling, and read seatmap_status to tell a real layout (ok) from no_layout_for_carrier. Top-level seatmap_status: "ok" when Tutu returned a schema, "no_layout_for_carrier" when Tutu has none for that train. The fallback is RARE — every train sampled live returned a layout, commercial brands included — so treat a layout as the norm and never predict its absence. In the fallback case cars=[] and an agent_hint field spells out that this is NOT 'no seats' — just 'no schema'. On success, cars[] carries every car of the train: {car_number, car_type, service_class, amenities[], seat_groups[], seats[], hints, canvas}. Each seat has number, type, compartment_number, deck (None unless double-decker), gender, position{x,y}, size{width,height}, plus precomputed distance_to_nearest_wc_px (Euclidean, server-side) so the agent quotes a ready number instead of doing geometry. The default view='compact' omits the raw geometry (position, size, nearest_wc_rect); pass view='full' to draw the car. For a pointed question prefer task= over paging the whole map: far_from_wc / female / together / summary return a short ranked answer across the WHOLE train (best seats + why), not every seat. task='together' (+ seats_together, default 2) answers «N мест рядом» directly — do NOT page the map and reason about coordinates or compartment numbers yourself. Seat type is an OPEN vocabulary: LOWER / UPPER / SIDE_LOWER / SIDE_UPPER / SEDENTARY are the common values, but variants occur live (LOWER_NEAR_WC, UPPER_NEAR_WC, SEDENTARY_WITH_PETS). Match by PREFIX, not equality: a lower berth is any type starting LOWER OR SIDE_LOWER (SIDE_LOWER does NOT start with LOWER), and SIDE_* is the separate side-of-car test. type == "LOWER" silently drops both LOWER_NEAR_WC and every side lower berth. Each seat also carries group_index — join key into cars[].seat_groups[], where service_class and the fare data live. Use group_index (NOT seat.type) for the join: two groups in one car CAN share a type with different service classes, so type alone would route the agent at the wrong fare. Moving the fare/class off the seat saves ~70 B per seat and keeps the wire under the 64 KB cap. Fares: seat_groups[].fares[] lists EVERY fare type of the group (one entry per fare_type, e.g. REFUNDABLE vs NON_REFUNDABLE; a missing upstream type is keyed "UNKNOWN"), cheapest-first. Each entry is {fare_type, price, child_price?}: price is the ADULT fare, child_price (when present) the CHILD fare of the same type — compare refundable vs non-refundable AND adult vs child from here, no checkout needed. PRICING CAVEAT (read pricing_note): these are pre-cart totals and run BELOW the final cart price — checkout applies Tutu's own larger service fee (observed +6–8%), while the search_rail listing price DOES match the cart. Use seatmap prices for fare-type/seat comparison; quote the bookable total from the search listing or say the final price appears in the cart. cheapest_fare equals fares[0] (kept for compatibility). A rare discounted: true on an entry means upstream returned only discounted prices for that fare type — do not present it as the standard adult fare. Top-level passenger_requirements carries the age rules per passenger type (e.g. CHILD max_age 10; BABY max_age 5 with needs_ticket=true, chargeable=false — an infant still needs a free ticket): caveat child fares with them; exact applicability is confirmed on Tutu checkout, and the passenger composition itself is entered by the user on the opened Tutu page / in the cart (the rail deeplink prefills seats, not the composition). Gender coupe policy is dynamic: read seat_groups[].is_gender and seats[].gender from the current response. Treat only gender="FEMALE" as a female seat and only gender="MALE" as a male seat; MIXED, NO_GENDER and UNDEFINED are not proof of a female/male compartment. Do not hardcode a compartment number as gendered because the policy can change as seats are sold. Final gender choice is confirmed on Tutu checkout; this tool is read-only. cars[].hints still exposes the (x,y,w,h) of WC, conductor, cooler, garbage icons if the agent needs the raw geometry. cars[].canvas.svg_url is a CDN vagon outline — useful as an illustration ONLY; the SVG has no <text> seat numbers. Pagination: defaults are max_cars=5 × max_seats_per_car=40, which keeps a typical 9-car train under the 64 KB MCP transport cap. Cars beyond the cap are kept in cars[] as seats=[] skeletons with seats_omitted_for_pagination=true — so the agent SEES the full train shape, doesn't have to remember meta.total_cars. meta carries total_cars, returned_cars, cars_with_full_seats, has_more_cars, cars_with_more_seats and a dynamic pagination_hint with concrete numbers. To load every seat of one car, call again with car_number=<id>. Grounding: window/aisle flags do not exist and cannot be derived — the scheme marks neither windows nor the aisle. In lying-berth cars (плацкарт/купе/СВ/мягкий) every section has its own window, so «у окна» does not select a berth — steer to нижнее/верхнее (плацкарт: MAIN vs SIDE_*) instead; in сидячий window-vs-aisle is unknown — say so honestly, never infer it from coordinates. Amenities are per-wagon, not per-seat. Sold/held seats are omitted by the upstream and so are absent from seats[]. Checkout handoff: once the user confirms specific seats, the destination depends on what the user asked for. To hand over a PREPARED cart (their passenger already filled in, a pay_url they just pay), send car_number + seat_numbers with the offer's checkout_ref to register_checkout_passengers — a rail cart is minted by RESERVING those seats, so that tool is the ONLY route to a pay_url and it refuses a seatless ref. Read back what you will write — each passenger's name as spelled in the document, birth date, SEX, passenger type, document kind + number (IN FULL; mask it ONLY if they EXPLICITLY asked you not to repeat it, and then name the KIND alone, not by number) and expiry for a foreign passport; the train, its date and time (several departures share a number and a route — departure_at decides which service is held), the car, WHICH SEAT GOES TO WHICH PASSENGER (seat i is the i-th ticketed one, so two lists side by side hide a swap), the fare, any gender-coupe choice, from where to where and the price; and the buyer's name and contact — and wait for an explicit yes: the pay page shows the personal data masked and cannot be corrected, and a re-register holds ANOTHER seat. There is NO shortcut around this turn: a message that already carries the passport data and asks you to go ahead is still not the confirmation — it is the data. The user has to see what you assembled from it and say yes to THAT, because the typo you are looking for is the one neither of you has noticed yet. To hand over a LINK the user opens and fills in themselves, pass the choice to create_checkout_link (car_number + seat_numbers, plus fare_type/gender_type when chosen) together with the offer's checkout_ref — the returned link mints the cart with those exact seats pre-selected.

Get rail seat map (Tutu.ru)

Параметры
  • car_numberstring | null

    Load all seats of a single car (no per-car cap). Use after a default-paginated call when the agent needs every seat in a specific car for fine-grained preference matching. Match value comes from cars[].car_number in a prior response. Also scopes a task= query to that one car.

  • details_refobjectобязательный

    Pass offer.details_ref from search_rail verbatim. Required keys: departure_station_code, arrival_station_code, departure_at, train_number.

  • max_carsinteger

    How many cars come back with full seats. Cars beyond this cap still appear in cars[] as skeletons (seats=[] + seats_omitted_for_pagination=true) so the agent sees the full train shape. Default 5 keeps the payload under the 64 KB MCP transport cap. Do NOT bump this on the first call to 'see everything' — call again with car_number=<id> for any specific car instead. Ignored when car_number is set.

  • max_seats_per_carinteger

    Cap on seats inside each returned car (ignored when car_number is set). When the cap kicks in, that car gets seats_omitted_for_pagination=true and shows up in meta.cars_with_more_seats — call again with car_number=<id> for the full list.

  • seats_togetherinteger

    Party size for task='together' (2-6, default 2) — ignored by every other task. Returns groups_by_car_type: per car category, up to 4 candidate groups of exactly this many free seats that share ONE section, cheapest-first. A group carries car_number, compartment_number, seat_numbers (ready for the checkout hand-off), per-seat {number, type, group_index}, the section's gender, service_class — derived from the CHOSEN seats' groups, so it is service_classes (a list) when the group spans two — spread_px (widest gap inside the group — smaller is tighter) and total_price + total_fare_type — the cheapest ADULT total the party can actually be sold at, priced in ONE fare type the seats have in common (checkout takes a single fare_type for the whole selection), omitted when they share none or currencies differ; it is a pre-cart comparison, NOT the final cart price — use the search listing for the bookable total or say the final price appears in the cart. deck appears on double-deckers and side on плацкарт (MAIN = compartment berths, SIDE = боковушки; a group NEVER mixes them, nor spans two decks). When no section has that many seats left, groups_by_car_type is {}. Read largest_group_available: when it is at least 2, best_available_groups_by_car_type (counted separately in best_available_groups_found_by_car_type) holds those smaller groups. When the maximum is 0 or 1, those fields are omitted because one seat is not a «together» group; use note, offer separate seats or another train, and do not call again with seats_together=1. Never present scattered seats as «рядом».

  • taskenum | null

    Optional focused query over the WHOLE train instead of the full per-car layout — returns a short ranked answer, not hundreds of seats: far_from_wc (farthest seats by distance_to_nearest_wc_px, ranked per berth type in seats_by_type so the best LOWER isn't hidden behind UPPER berths — keys are RAW upstream types, so a lower berth is any key starting LOWER or SIDE_LOWER, not just LOWER); female (seats currently gender="FEMALE", capped with a total_female_seats count + dynamic-policy caveat); together (sets of seats_together free seats sharing ONE section of one car — for «двое рядом», see seats_together); summary (per-car available-seat counts by berth type, no seat list). Leave unset for the normal paginated seatmap. When set, view / max_cars are ignored; car_number optionally scopes the task to one car (e.g. task='female', car_number='5' to narrow a long female list).

  • viewenum

    Response detail level: compact (default) or full. compact drops the per-seat rendering geometry (position, size, nearest_wc_rect) that the agent never needs — decisions use the precomputed distance_to_nearest_wc_px and the seat attributes, both kept. full restores the raw geometry for clients that draw the car. Ignored when task is set.

register_checkout_passengersвнешний мир

NOT the default booking handle — that is create_checkout_link («забронируй» included): normally hand the link first, offer this tool as the opt-in next step, and reach for it only when the user explicitly agrees to hand over passport data — never open the exchange by asking for documents, and never register before the user has confirmed the summary of what will be written. Register the passenger(s) the USER gave you (passport data) onto a Tutu checkout cart and return a pay_url — a hand-over link the user opens to PAY in their own browser session (it is a pay page, not a data-review page — read the data back first). For a FLIGHT this BOOKS: it places a real (unpaid) Tutu order before filling the cart (trains, buses and hotels only mint a cart). It never CHARGES — the user pays on the linked page — but it is not a dry run: call it only after the user has confirmed THIS exact offer and given their passport data. The response carries the order_number. READ THE DATA BACK FIRST. The pay page is a pay button over a MASKED summary — the personal data shows there as asterisks and nothing on it can be corrected — so your own reply before this call is the only place a typo can surface, and a wrong letter costs a NEW cart (on a flight, a SECOND unpaid order). So before calling, read back what you are about to register: every passenger's name in the document's own spelling, birth date, SEX, passenger type, document kind + number (+ expiry for a foreign passport) — SEX especially, because you usually infer it from the name rather than being told it, and a wrong one mis-registers the passenger on their ticket; the trip itself — FROM WHERE TO WHERE (the refs carry opaque geo ids, so the endpoints have to be said in words), the service, date, time, car/seats, class or room, price (on a train also which seat goes to which passenger, the fare and any gender-coupe choice; on a hotel the property and its city, the room and rate); and the buyer's name plus the email/phone the tickets go to (the buyer's own name is written to the cart too, even when they are not travelling). Then wait for the user to confirm it. Show the document number IN FULL — that is the default and it is not yours to soften: a typo in the number is exactly what this check is for, and the user typed it in this same conversation, so masking protects nothing and breaks the check. That turn is UNCONDITIONAL: a message that already carries the passport and says «оформи» supplies the DATA, not a confirmation of what you assembled from it — and the typo you are hunting is the one nobody has noticed yet. The one narrow exception is about DETAIL, not about skipping the turn: a user who EXPLICITLY asked you not to repeat their document gets it named by KIND, not by number, with everything else still read back. That request is the ONLY thing that hides the number — absent it, show it. The pay_url is SINGLE USE and lives ~1 hour: the FIRST thing to open it gets the cart, everyone else gets a 404 (verified live). Never open or fetch it yourself to 'check' it — that spends it — and beware messenger link previews, which fetch URLs automatically. Hand it to the user as plain text, tell them it's theirs alone and good for about an hour. If it lapses or gets spent there is no cheap refresh: calling this tool again redoes the work (for a flight, a SECOND unpaid order), so ASK the user first. Give the offer EXACTLY ONE way: - avia_offer (the normal path for a flight): pass the search offer's checkout_ref fields — offer_hash, departure_geo_city_id, arrival_geo_city_id, departure_at, service_class, the passengers_* counts (they MUST match the passengers you send — they price the booking), plus return_departure_at for a round trip. We resolve it into the cart ourselves. - offer_pack_id + product_source="hotels" (the normal path for a HOTEL): pass a ROOM rate's offerpack_hash from get_offer_details. The listing's best_offer.offerpack_hash is NOT one — it does not mint a cart. - offer_pack_id + product_source: any other checkout-gw offerPackId you were GIVEN. An avia search_* offer id / variant uuid is NOT one (the cart API rejects those), so never derive it from a flight search — use avia_offer for that. - rail_offer (the normal path for a TRAIN): the search offer's WHOLE checkout_ref plus the car_number / seat_numbers the user picked in get_rail_seatmap — one seat per TICKETED passenger (adult or child; an INFANT rides free without a seat), in the SAME ORDER as passengers. Reserving the seats needs the ref's departure_station_code / arrival_station_code, so don't trim the ref. Places NO order and charges nothing, but it DOES hold the chosen seat(s) with the carrier — so a repeat call holds ANOTHER seat; don't retry a train call without the user's OK. A TRAIN cart is minted by RESERVING those seats, so the seat is not optional paperwork: there is no seatless rail cart and no `pay_url` without a seat, here or on tutu.ru. No seats yet → call get_rail_seatmap and HELP the user choose — read the map and propose seats that fit what they asked for, then send the choice they confirm because the reservation holds real inventory — always, with no autonomous shortcut. Handing a LINK instead is never a downgrade: create_checkout_link takes the same seat choice and mints the cart from the link, with no passport data — prefer it whenever the user would rather fill the cart themselves. If get_rail_seatmap answers seatmap_status="no_layout_for_carrier" there is no layout to pick from, so the seats are chosen on Tutu: hand the user create_checkout_link's seat page — the CORRECT outcome for such a train (a checkout_url, not a pay_url), not a defect to report. - bus_offer (the normal path for a BUS): the search offer's checkout_ref. Places NO order. It carries NO seat choice, and the pay page is a payment summary rather than a seat picker — do not promise the user they will pick seats there; hand create_checkout_link (which takes seat_numbers) if the seat matters to them. The party must match what the offer was priced for: checkout_ref.passengers in total and, when the ref carries the split, passengers_adult / passengers_child per voyager_type — a child priced on a child fare can't be registered as an adult (or vice versa). Each call mints a FRESH cart, so don't retry it casually. passengers: one object per traveller — first_name, last_name, middle_name?, birthday (ISO-8601), sex (MALE/FEMALE), document, voyager_type? — ADULT (default), CHILD or INFANT; the party must match the offer's passengers_* counts. On a TRAIN a CHILD books the child fare and an INFANT rides free (no seat, ticket still issued) — quote child prices from get_rail_seatmap (child_price), and mind the age rules (passenger_requirements): each supplied birthday must fit the chosen CHILD/INFANT type on the departure date, or the call is refused before the cart is minted. document: say WHAT the user gave you — document_type is a KIND: RUSSIAN_PASSPORT (внутренний паспорт РФ), FOREIGN_PASSPORT (загран), BIRTH_CERTIFICATE, SIMPLE_DOCUMENT (hotels) — plus number and expires_at (required for a foreign passport). We pick the exact type this cart wants; you do NOT need to know that a flight calls the same passport something else than a train does. Names must be in the document's own script: a Russian internal passport on a TRAIN/BUS needs CYRILLIC names, while flights and foreign passports need the LATIN spelling AS PRINTED in the document. Ask the user for the right spelling — never transliterate it yourself (a name that doesn't match the document gets the passenger turned away). A mismatch fails before anything is written. customer: the buyer's contact — first_name, last_name, email, phone. The tickets go to this email/phone, so read it back with the passengers. Returns {pay_url, pay_url_expires_at, cart_id, passengers_registered, validated, ...} — a COUNT, not the registered data: the read-back above comes from the arguments you sent, which is what we write — we map the per-product document TYPE and canonicalise date FORMAT for the gateway (a bare 1990-01-01 becomes 1990-01-01T00:00:00Z, same day), but never a name, a number, or the VALUE of a date. Hand pay_url to the user (it expires ~60 min after minting). Collect passport data ONLY with the user's clear consent; this tool sends it to Tutu to fill the cart. Keeping that data out of logs is a SEPARATE rule from showing it to its owner — the read-back is required, not a leak; just don't repeat it afterwards without a reason. Optional marketing/personal-data consents are NOT accepted on the user's behalf.

Register passenger(s) on a cart and get the pay link (Tutu.ru)

Параметры
  • avia_offerany | null

    The flight to book, from the search offer's checkout_ref. Resolving it PLACES an unpaid booking. Read this trip back to the user with the passengers before the call — what you are about to buy: from where to where, every flight with its date, time and cabin class, and the price. The user's yes to that summary is required, always. Provide exactly one offer source.

  • bus_offerany | null

    The bus trip to book: the search offer's checkout_ref (hash, the city ids, departure_at). Books NOTHING and charges nothing. It carries NO seat choice, and the pay page is a payment summary rather than a seat picker — if the seat matters to the user, hand create_checkout_link (which takes seat_numbers) instead. Read this trip back to the user with the passengers before the call — what you are about to buy: WHICH BUS (the carrier and service — several operators run the same route minutes apart and this hash is what picks one), from where to where, the date and time, and the price. The user's yes to that summary is required, always. Provide exactly one offer source.

  • customeranyобязательный

    The buyer's contact (name, email, phone). All three are written to the cart — the buyer's name too, even when they are not travelling — and the tickets go to this email/phone. Read the buyer's name, email and phone back with the passengers and the trip, and wait for the user's yes before this call — unconditionally, as for passengers: a message that already carries these values is the data, not the confirmation.

  • offer_pack_idstring | null

    A checkout-gw offerPackId: for a HOTEL, a ROOM rate's offerpack_hash from get_offer_details (with product_source='hotels'). NOT an avia search offer id / variant uuid — the cart API rejects those; use avia_offer for a flight. Read this trip back to the user with the passengers before the call — what you are about to buy. This path takes ANY product's pack (see product_source), so read back whichever applies: the trip — from where to where, and WHICH offer on it, since the refs are opaque hashes: on a FLIGHT every leg of the itinerary with its date, time and cabin class (a confirmed economy is not a confirmed business, and an unchecked return or connection is still booked); on a TRAIN the train, its date and time, the CAR and WHICH SEAT GOES TO WHICH PASSENGER (the same seat number exists in several cars, and seat i is the i-th ticketed passenger — two lists side by side hide a swap), the fare and any gender-coupe choice; on a BUS the carrier and service, since several operators run the same route minutes apart; on a HOTEL the property and its city, the room and rate, and the check-in / check-out dates — plus the price. The user's yes to that summary is required, always. Provide exactly one offer source.

  • passengersany[]обязательный

    Travellers to register (one per seat). READ THEM BACK to the user and wait for an explicit yes BEFORE this call — each name in the document's own spelling, birth date, SEX, passenger type, document kind + number (+ expiry). Include SEX: you usually infer it from the name instead of being told it, so it is the field most likely wrong. The pay page shows this data masked and cannot be corrected, so a typo costs a NEW cart (on a flight, a SECOND unpaid order). The turn is UNCONDITIONAL — a message that already carries these values supplies the DATA, not a confirmation of what you assembled from it. The one exception is about DETAIL: a user who EXPLICITLY asked you not to repeat their document gets it named by KIND, not by number. That request is the only thing that hides it — by default the number is shown IN FULL.

  • product_sourceenum | null

    Product of the offer pack (required with offer_pack_id): avia / rail / bus / hotels.

  • rail_offerany | null

    The train to book: the search offer's WHOLE checkout_ref plus the car_number / seat_numbers the user picked via get_rail_seatmap and CONFIRMED — one seat per TICKETED passenger (adult or child), in the SAME ORDER as passengers (seat i goes to the i-th ticketed passenger); an INFANT rides free without a seat. The seats are REQUIRED (the cart is minted by reserving them; without them use create_checkout_link's seat page instead). Places NO order and charges nothing, but it DOES hold the chosen seat(s) with the carrier — a repeat call holds ANOTHER seat, so don't retry a train call without the user's OK. Read this trip back to the user with the passengers before the call — what you are about to buy: from where to where, the train, its date and time, the car, which seat goes to which passenger, the fare, any gender-coupe choice, and the price. The user's yes to that summary is required, always. Provide exactly one offer source.

search_aviaтолько чтениеидемпотентныйвнешний мир

Search Tutu air tickets between two cities or specific airports. PRICE BASIS — every price already covers the WHOLE searched party (adults + children + infants; meta.pricing.basis="party_total", verified live). Never multiply it by the passenger count. NB the sibling tools differ: search_rail / search_etrain quote ONE SEAT. origin/destination accept a city ('Москва'), an airport name ('Шереметьево') or a bare IATA code ('SVO', 'IST'). An airport input narrows the city-wide search to that airport: offers via the city's other airports are dropped (count in meta.post_filter_dropped_wrong_airport; the key is present only when the request named a specific airport), the endpoint gets meta.from/to.kind="airport" and its iata becomes the airport's own code. A bare code means the airport, not the metro area ('IST' = Стамбул-Новый only, SAW dropped); pass the city name to cover all its airports. When every city flight uses other airports, meta.airport_note lists them with counts — relay it and offer a city-wide retry. meta.from/to echo the resolved place (name, geo_id, region); match + query are added only when the resolved name differs from the wording sent (lead = a short form or typo read by Tutu's own ranking — say which city came back and keep going; qualified = the trailing words acted as a region filter; prefix), and also_named[] lists same-name places that lost — mention them, never as a blocking question. An ambiguous or unknown name is REFUSED with could not resolve … (the equal candidates with regions, or a note that only Tutu's hotel index lists the place — then offer search_hotels), never searched as another city: ask the user or resend as '<name>, <область>' (name first). A place without an airport is refused too; the text names the city Tutu's avia index answers with (Сочи for Хоста) — OFFER that city and search it only after the user agrees, never pick an airport city on your own. Supports one-way and round-trip (return_date). Each offer carries: price (cheapest fare), variants[] (every fare family with its own offer_hash for checkout), legs[].segments[] (full itinerary with voyage_no flight numbers, including review_summary for the carrier when Tutu returns feedback), search_results_url (tutu.ru listing page — not a per-offer URL), and checkout_ref (pass verbatim to create_checkout_link to get a deeplink purchase URL for the exact ticket plus a search-results URL for browsing — show the deeplink when the user has chosen this specific flight. A Direct ROUND-TRIP offers are deeplinkable too: checkout_ref carries is_round_trip + return_departure_at — forward BOTH and the deeplink mints a cart with both legs. Connecting round-trips fall back to search because explicit/avia currently supports one flight per direction; omitting return_departure_at also falls back to search. checkout_ref also carries passengers_full/child/infant from the search so the cart can request the same party — forward them, else the cart opens with one adult. An offer flagged is_multi_pnr (one-way OR round-trip) means separate tickets / self-transfer — relay its multi_pnr_note; on a ONE-WAY such an offer also gets checkout_ref.checkout_parts, which must be forwarded because create_checkout_link cannot put such a trip in one link — it answers with one link per ticket). For a presentation card, pass the selected compact offer object as details_ref to get_offer_details(product_type='avia', ...); there is no separate read-only avia detail endpoint. Paginated (page+page_size, see meta.has_more) — a page is a WINDOW over the matched flights, NOT the day's timetable: a flight absent from the current page may still operate, so never answer «такого рейса нет» from one page while has_more is true; when the user names a flight, pass flight_numbers instead of paging blind — and an empty NUMBER-ONLY result means «нет в продаже на Tutu на эту дату» (the flight may still operate), never a timetable claim — and only while meta.total_matched_exact is true: on a rare capped route (false) the filter saw a truncated pool, so the check is INCONCLUSIVE — say that instead of «нет». With other filters also active (price_max / carriers / direct_only / airport scope) even that much doesn't follow — the flight may be dropped by one of those (see meta.post_filter_dropped_*); re-run number-only before concluding. Sortable by price / duration / departure time. Filterable server-side: direct_only (nonstop only), carriers (by airline) and flight_numbers (by flight designator). meta.carriers_available is the per-route airline reference (name + offers_count + price_from, derived from the fetched route pool; a stable id is added only when Tutu ships its carrier facet) — filter by echoing a name from it rather than guessing spelling. Filter drop counts surface in meta.post_filter_dropped_not_direct / ..._dropped_wrong_carrier / ..._dropped_wrong_flight_number. With a filter active the whole route pool is fetched before filtering, so a carrier listed in carriers_available reliably appears and has_more stays honest; meta.total_matched is the exact count after filters, before pagination (meta.total_matched_exact=false only on a rare >200-offer route, where it is a lower bound). Grounding: every claim about baggage, cabin baggage, refund, change, seat selection, fare family or carrier rating must be traceable to variants[].conditions or review_summary in this response. If a field is null/absent, say so explicitly — never fall back to general knowledge of the carrier or to web search. Airport disambiguation is mandatory: each legs[].segments[].to is a self-describing string "City — Name (IATA), терм. T" (e.g. "Стамбул — Сабиха Гёкчен (SAW)") — surface the airport name and terminal verbatim, and call out when two offers in the same list use different airports of the same city (SAW vs IST for Стамбул, SVO / DME / VKO / ZIA for Москва). Say that they are different airports, but do not add distance, side-of-city or transfer facts absent from the payload. When the upstream omitted the name the string falls back to "City, IATA"; then IATA → airport-name is acceptable common knowledge (stable global registry). meta.cashback is normally ABSENT here — flights earn none today — so say nothing about cashback unless the key is present. When it is, it states the whole page's Tutu loyalty rate (rate_pct; applies_to = all_fares / most_fares / some_fares with rows_at_rate of rows_total fare rows; exceptions[] for the rows that differ): bonus points credited to the buyer's Tutu account after payment, NOT money off price, never to be presented as a discount. For airport disambiguation and checkout details call get_avia_instructions.

Search air tickets (Tutu.ru)

Параметры
  • adultsinteger

    Adult passengers (12+ years).

  • carriersstring[] | null

    Keep only offers by these airlines. Pass a name from meta.carriers_available (e.g. 'Аэрофлот'); an id also works when that entry carries one. Do NOT guess spelling: 'aeroflot' will not match 'Аэрофлот'. Case-insensitive substring on the display name; all carriers on a multi-carrier offer must match one of the requested values. An empty list is a no-op. Dropped count in meta.post_filter_dropped_wrong_carrier.

  • childreninteger

    Children (2..11 years).

  • departure_datestring | null

    Outbound departure date, YYYY-MM-DD.

  • destinationstring | null

    Destination city or a specific airport by name/IATA code ('Шереметьево', 'SVO') — an airport narrows results to it. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.to then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). to_city is accepted as a backward-compatible alias.

  • direct_onlyboolean

    Keep only nonstop flights (every leg a single segment; correct for round-trip). Client-side post-filter; dropped count in meta.post_filter_dropped_not_direct.

  • flight_numbersstring[] | null

    Keep only offers containing one of these flights — THE way to answer «возьми рейс N»: one call instead of paging through the day. Accepts the full designator in any spelling ('SU-6176', 'SU 6176', 'su6176' — case, separators and leading zeros are normalized) or the bare flight digits ('6176', matched against every segment's number). ANY-segment match: a connection or round-trip containing the named flight keeps the WHOLE itinerary. Applied client-side to the full route pool (upstream has no flight-number filter); an empty list is a no-op. Dropped count in meta.post_filter_dropped_wrong_flight_number. An empty NUMBER-ONLY result means «этого рейса нет в продаже на Tutu на эту дату» — the flight may still operate (sold out or not sold here); never turn it into a timetable claim. That reading itself holds only while meta.total_matched_exact is true — on a rare capped route (false) the filter saw a truncated pool and the check is inconclusive. Combined with other filters it proves even less — the flight may be dropped by price/carrier/direct/airport scope (see their meta.post_filter_dropped_* counters); re-run number-only before concluding.

  • from_citystring | null

    Deprecated alias for origin.

  • infantsinteger

    Infants (0..1 years).

  • originstring | null

    Origin city (e.g. 'Москва', 'Moscow', 'Сочи') or a specific airport by name/IATA code ('Внуково', 'VKO') — an airport narrows results to it. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.from then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). from_city is accepted as a backward-compatible alias.

  • pageinteger

    1-indexed page number. Each page carries up to page_size offers. Check meta.has_more to know if another page exists.

  • page_sizeinteger

    Offers per page (1..30). Default 10.

  • price_maxnumber | null

    Hard cap on price per offer (RUB). Enforced server-side.

  • return_datestring | null

    Optional return date, YYYY-MM-DD. When set, the tool asks upstream for a round-trip package and returns offers with both legs (legs[0]=outbound, legs[1]=return). Crucial for long-haul international routes — some city pairs (e.g. Tokyo→Moscow far-future) are sold only as round-trip and return 0 offers as one-way.

  • service_classstring

    IATA service class: Y=economy, S=premium economy, C=business, F=first.

  • sortenum

    Ordering applied before pagination. price_asc (cheapest first), price_desc, duration_asc (shortest first), departure_asc (earliest departure).

  • to_citystring | null

    Deprecated alias for destination.

  • viewenum

    Response detail level: compact (default) or full. compact returns lean decision cards — rail collapses the long per-class fare list to a fares summary {count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?} (the full per-class breakdown and car classes come from get_offer_details; per-variant conditions: {refundable, changeable} come with view='full'), and rail/bus drop the per-segment rating that duplicates the offer-level one. Avia and etrain are left unchanged — neither has a read-only detail endpoint to recover dropped fares/ratings from. full inlines every fare variant and per-segment review_summary. Prefer compact for first-pass search; pass full only when the user needs the whole fare breakdown inline for several offers at once.

search_busтолько чтениеидемпотентныйвнешний мир

Search Tutu intercity bus tickets between two cities. Each offer carries: price, variants[] (where the carrier offers several tariffs), legs[].segments[] with stop names and carrier ratings in review_summary when Tutu returns feedback, and search_results_url (tutu.ru listing). Offers deliberately carry NO ready checkout link — the explicit/bus deeplink comes from create_checkout_link(checkout_ref). meta.from/to echo the resolved place (name, geo_id, region); match + query are added only when the resolved name differs from the wording sent (lead = a short form or typo read by Tutu's own ranking — say which city came back and keep going; qualified = the trailing words acted as a region filter; prefix), and also_named[] lists same-name places that lost — mention them, never as a blocking question. An ambiguous or unknown name is REFUSED with could not resolve … (the equal candidates with regions, or a note that only Tutu's hotel index lists the place — then offer search_hotels), never searched as another city: ask the user or resend as '<name>, <область>' (name first). PRICE BASIS — prices cover the WHOLE searched party (adults + children; meta.pricing.basis="party_total"), so never multiply a price by the passenger count. NB search_rail / search_etrain differ: they quote ONE SEAT. checkout_ref carries that composition (passengers total + passengers_adult / passengers_child) — forward it verbatim so registration books the same party. checkout_ref can rebuild the link, especially for a non-cheapest tariff. details_ref can be passed to get_offer_details(product_type='bus', ...) to load stops, carrier, structured amenities[] (wifi / air_conditioning / power_outlet / toilet flags as machine-readable codes), ticket (eticket flag + accepted documents), refund.blocks[] / luggage.blocks[] and seat_selection (free_count, available_seat_ids, has_scheme). Use available_seat_ids as seat_numbers only after the user asks you to choose/preselect seats; if it is empty while free_count is positive, exact-seat preselection is unavailable in this response, not 'no seats'. For amenity code → label localization see tutu://amenities/dictionary. Paginated + sortable. meta.cashback states the Tutu loyalty accrual ONCE for the whole page: rate_pct plus applies_to = all_fares / most_fares / some_fares. Under all_fares every fare row earns the same rate, so it CANNOT decide which bus to take — don't rank on it. Under most_fares / some_fares the rate is NOT uniform and can genuinely favour one fare: rows_at_rate of rows_total fare rows says how far from universal it is (read those two, not the list length — the list may be capped), and exceptions[] names what differs — an offer_id, its price, and a variant_id when a campaign prices one fare family only (exceptions_total when that list was capped). Quote the row by its price. Fare rows carry no cashback of their own, so that list is the only place the difference shows. Either way these are bonus points credited to the buyer's Tutu account after payment — they do NOT reduce price; never present them as a discount. Absent when nothing on the page earns any. Grounding: stop names, luggage rules and carrier facts must come from this response or get_offer_details — never show raw stop ids as the only stop description and never invent luggage / boarding rules when the field is missing. See get_bus_instructions.

Search bus tickets (Tutu.ru)

Параметры
  • adultsinteger

    Number of adult passengers.

  • carriersstring[] | null

    Keep only offers by these carriers (e.g. 'Ecolines'). Pass a name from meta.carriers_available (the operators present in this result set); case-insensitive substring, an empty list is a no-op. All carriers on a multi-carrier offer must match one of the requested values. Dropped count in meta.post_filter_dropped_wrong_carrier.

  • childreninteger

    Number of children travelling on a child fare (each occupies a seat; the age limit and any discount are carrier-specific — never promise a discount the price doesn't show). Offer prices cover the WHOLE searched party (adults + children), max 8 seats total.

  • departure_datestring | null

    Departure date, YYYY-MM-DD. Use the exact departure_date key; date is not an accepted alias.

  • destinationstring | null

    Destination city. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.to then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). to_city is accepted as a backward-compatible alias.

  • direct_onlyboolean

    Keep only direct buses (no transfers — every leg a single segment). Dropped count in meta.post_filter_dropped_not_direct.

  • from_citystring | null

    Deprecated alias for origin.

  • originstring | null

    Origin city. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.from then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). from_city is accepted as a backward-compatible alias.

  • pageinteger

    1-indexed page number.

  • page_sizeinteger

    Offers per page.

  • price_maxnumber | null

    Hard cap on price per offer (RUB). Enforced server-side.

  • sortenum

    Ordering applied before pagination.

  • to_citystring | null

    Deprecated alias for destination.

  • viewenum

    Response detail level: compact (default) or full. compact returns lean decision cards — rail collapses the long per-class fare list to a fares summary {count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?} (the full per-class breakdown and car classes come from get_offer_details; per-variant conditions: {refundable, changeable} come with view='full'), and rail/bus drop the per-segment rating that duplicates the offer-level one. Avia and etrain are left unchanged — neither has a read-only detail endpoint to recover dropped fares/ratings from. full inlines every fare variant and per-segment review_summary. Prefer compact for first-pass search; pass full only when the user needs the whole fare breakdown inline for several offers at once.

search_etrainтолько чтениеидемпотентныйвнешний мир

Search Tutu suburban / commuter trains (электрички) between two cities. Useful for short routes around Москва / СПб / regional centres. origin/destination take a city or a station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). meta.from/to echo the resolved place (name, geo_id, region); match + query are added only when the resolved name differs from the wording sent (lead = a short form or typo read by Tutu's own ranking — say which city came back and keep going; qualified = the trailing words acted as a region filter; prefix), and also_named[] lists same-name places that lost — mention them, never as a blocking question. An ambiguous or unknown name is REFUSED with could not resolve … (the equal candidates with regions, or a note that only Tutu's hotel index lists the place — then offer search_hotels), never searched as another city: ask the user or resend as '<name>, <область>' (name first). PRICE BASIS — every price here is ONE SEAT (meta.pricing.basis="per_seat"). This search takes no party size at all, so nothing is totalled for you: for N travellers multiply and present the result as «от». Do not assume the whole-party pricing of search_avia / search_bus. Each offer carries the same shape as search_rail: price, variants[], legs[].segments[], search_results_url, checkout_url (tutu.ru commuter schedule page with st1 / st2 route ids), and checkout_ref for rebuilding the link if needed. Each segment carries vehicle_meta ({name, code, description, seat_assignment}) when Tutu's dictionary provides it — use this to filter consist type without parsing labels. seat_assignment is the ONLY field to answer «билет с местом или просто сел и поехал?» from: reserved means Tutu states the ticket names a seat (description is the sentence saying so); unknown means Tutu did not state it — say that out loud and quote description for what it DID say, never call it free seating. NB swallow («Ласточка (с местом)») and swallowBlue («Ласточка») are different consists that differ in exactly this, so read code, not the name. No separate read-only detail endpoint is confirmed for etrain; get_offer_details(product_type='etrain', details_ref=<offer>) reformats the selected search offer with a ticket block (eticket, by_passport) and surfaces vehicle_type in summary. Paginated + sortable. meta.cashback is normally ABSENT here — commuter trains earn none today — so say nothing about cashback unless the key is present. When it is, it states the whole page's Tutu loyalty rate (rate_pct; applies_to = all_fares / most_fares / some_fares with rows_at_rate of rows_total fare rows; exceptions[] for the rows that differ): bonus points credited to the buyer's Tutu account after payment, NOT money off price, never to be presented as a discount. Grounding: schedule, stops and carrier facts must come from this response — do not substitute web lookups or general regional-rail knowledge when a field is missing. See get_etrain_instructions.

Search commuter trains (Tutu.ru)

Параметры
  • carriersstring[] | null

    Keep only offers by these carriers. Accepted for parity; commuter offers often carry no named carrier, so this frequently drops everything or nothing — inspect meta.carriers_available first. Case-insensitive substring; empty list is a no-op. All carriers on a multi-carrier offer must match one of the requested values. Dropped count in meta.post_filter_dropped_wrong_carrier.

  • departure_datestring | null

    Departure date, YYYY-MM-DD.

  • destinationstring | null

    Destination city or station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.to then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). to_city is accepted as a backward-compatible alias.

  • direct_onlyboolean

    Keep only single-segment commuter offers. Accepted for interface parity with the other transports; on etrain it is nearly always a no-op (commuter offers are single-segment). Dropped count in meta.post_filter_dropped_not_direct.

  • from_citystring | null

    Deprecated alias for origin.

  • originstring | null

    Origin city or station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.from then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). from_city is accepted as a backward-compatible alias.

  • pageinteger

    1-indexed page number.

  • page_sizeinteger

    Offers per page.

  • price_maxnumber | null

    Hard cap on price per offer (RUB). Enforced server-side.

  • sortenum

    Ordering applied before pagination.

  • to_citystring | null

    Deprecated alias for destination.

  • viewenum

    Response detail level: compact (default) or full. compact returns lean decision cards — rail collapses the long per-class fare list to a fares summary {count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?} (the full per-class breakdown and car classes come from get_offer_details; per-variant conditions: {refundable, changeable} come with view='full'), and rail/bus drop the per-segment rating that duplicates the offer-level one. Avia and etrain are left unchanged — neither has a read-only detail endpoint to recover dropped fares/ratings from. full inlines every fare variant and per-segment review_summary. Prefer compact for first-pass search; pass full only when the user needs the whole fare breakdown inline for several offers at once.

search_hotelsтолько чтениеидемпотентныйвнешний мир

Search Tutu hotel listings for a given city and date range. Resolve city_name (string) OR pass geo_id (Tutu internal). Returns hotels with name, stars, rating, address, hotel_id / hotel_geo_id (numeric id for get_offer_details), tutu_offer_id (listing UUID, not accepted by details), review_summary (when Tutu has reviews), location, photos, best_offer (price + offerpack_hash + checkout_url + room_size_sqm (room area in m² when Tutu wrote it in the room name, often as a minimum 'от N'; null when absent — say so, don't infer) + normalized rate features: breakfast_included, meal_name, free_cancellation, pay_at_hotel, pay_online, plus raw highlights[] for promo/context badges) — here checkout_url IS a real per-hotel page (hotel.tutu.ru/offers/details?...) with dates and room composition pre-filled, unlike the transport tools. For checkout PREFER create_checkout_link with the row's checkout_ref — the explicit/hotel deeplink opens the hotel page to pick a room, or mints the cart straight-to-cart when you pass a ROOM rate's offerpack_hash from get_offer_details (the listing best_offer.offerpack_hash does NOT mint a cart); best_offer.checkout_url stays the direct-page fallback when the tool's link isn't available. Filters: stars, meals, hotel_types, min_rating, price_max, free_cancellation, breakfast_included, hotel_amenities (e.g. pool, parking, wifi, spa), room_amenities (e.g. sea_view, balcony, air_conditioner). Hotels Tutu adds only as looser alternatives are dropped, so under filters an empty hotels list means Tutu has no match on these dates — say so and offer to relax a filter. Paginated (page+page_size, see meta.has_more). Read-only, no booking. When the request is broad, many hotels are viable, and you don't know the user's preferences — do NOT call this tool yet: ask one short batch of clarifying questions FIRST (bed setup, breakfast, free-cancel, view/location; 2-4 questions max). But that gate is OFF whenever the user hands you the decision — a deterministic selection rule ('первый найденный', 'самый дешёвый', 'выбери сам', 'лишь бы переночевать', 'без уточняющих вопросов') or an explicit checkout intent ('оформляй', 'кинь ссылку'). Then the preferences you weren't told are the ones that don't matter: search now, apply their rule to the results and hand over the link in the SAME turn. The only thing still worth asking for is a REQUIRED field you cannot fill — the city and the dates, which this tool rejects the call without; never invent those. A preference is never that field. Judge that intent, don't pattern-match those examples as a closed list. Generic positive framing ('хорошее место', 'бюджет не критичен') is NOT a selection rule — still ask. When the per-room / per-rate confirmation field for the asked-for feature is missing or mismatched — rooms[i].view / rooms[i].bed_type for view/bed, or rooms[i].rates[j].breakfast_included / rooms[i].rates[j].free_cancellation for the rate-level facets, or best_offer.{breakfast_included,free_cancellation} when only listing data is available — label the status next to the recommendation; never substitute silently. Full rules in get_hotels_instructions (clarifying questions, geo_id pitfall, view/bed grounding). Grounding: rating, review count, amenities and pros/cons must come from review_summary, review_summary.aspects and from get_offer_details(product_type='hotel', ...). The best_offer fields describe ONLY the cheapest visible rate of one room — to compare room categories (standard / comfort / suite, beds, view, size) and full per-rate options, call get_offer_details. Never invent guest quotes or substitute generic city-level praise when the hotel-level field is empty. best_offer.price is the TOTAL for the whole requested stay and guest composition (price_basis="stay_total", the night count already included) — render it as-is and NEVER multiply it by the number of nights; stay.nights is there only to label it 'за N ночей'. See get_hotels_instructions.

Search hotels in a city (Tutu.ru)

Параметры
  • adultsinteger

    Number of adult guests.

  • breakfast_includedboolean | null

    Filter: keep only offers whose cheapest rate includes breakfast. Shortcut for meals=['breakfast'].

  • check_instring | null

    Check-in date, YYYY-MM-DD. checkin_date is accepted as a backward-compatible alias.

  • checkin_datestring | null

    Deprecated alias for check_in.

  • check_outstring | null

    Check-out date, YYYY-MM-DD. checkout_date is accepted as a backward-compatible alias. Must be after check_in.

  • checkout_datestring | null

    Deprecated alias for check_out.

  • children_agesinteger[] | null

    Ages of accompanying children, e.g. [6, 12]. Empty/None = no children.

  • city_namestring | null

    City name (Russian; English/translit also accepted). Resolved via Tutu's hotel-specific, region-aware geo index: a resort name lands the whole zone (e.g. «Курорт Архыз» covering several посёлки), a city lands that city. meta.resolved_geo reports geo_type (region|locality), hotels_count and also_geo alternatives. PREFER this over passing geo_id directly. Mutually-optional with geo_id.

  • free_cancellationboolean | null

    Filter: keep only hotels offering free cancellation. Verify the cheapest rate with best_offer.free_cancellation per row.

  • geo_idstring | null

    Tutu hotel city geo_id (e.g. '2657260' for Moscow, '2656873' for Казань). Skip the city_name lookup if provided. WARNING: only pass an id obtained from a previous search_hotels response (meta.geo_id or meta.resolved_geo.geo_id). Do NOT reuse geo_ids from transport tools (search_avia / search_rail / search_bus / search_etrain) — for some cities those resolve to an AIRPORT/STATION entry that has zero hotels in Tutu's hotel catalog.

  • hotel_amenitiesenum[] | null

    Filter: property-level amenities, listed aliases only (not numeric ids). All must be present: ['pool', 'parking'] = pool AND parking.

  • hotel_typesenum[] | null

    Filter: property types, any of the listed (e.g. санаторий -> 'sanatorium', глэмпинг -> 'glamping').

  • mealsstring[] | null

    Filter: meal plans, e.g. ['breakfast', 'halfboard', 'allinclusive', 'fullboard', 'lunch', 'dinner', 'nomeal'].

  • min_ratingnumber | null

    Filter: minimum aggregated rating (0..10). Mapped to the nearest Tutu rating bucket (>0 / >7 / >8 / >9).

  • pageinteger

    1-indexed page number.

  • page_sizeinteger

    Hotels per page.

  • price_maxinteger | null

    Filter: maximum price PER NIGHT (RUB). Sent to the upstream as a relevance signal, then enforced server-side as a hard cap on best_offer.price.amount / stay.nights — the offer price is a whole-stay total, so it's divided back to per-night before the comparison.

  • room_amenitiesenum[] | null

    Filter: room-level amenities, listed aliases only (not numeric ids). All must be present. Best-effort at the listing upstream — the response carries one best_offer, so use get_offer_details for guaranteed per-room filtering.

  • starsinteger[] | null

    Filter: include only these star ratings (1..5, plus 0 for unrated). Multi-select.

  • viewenum

    Response detail level: compact (default) or full. compact keeps one cover photo per hotel; full keeps a small gallery (a handful of photos, not every one — photos_total records the real count, the rest live on the hotel page). Room categories, rate ladders and review texts come from get_offer_details regardless of view. Prefer compact for first-pass search — full only swaps in more photos, which an agent rarely needs and which can overflow client output caps.

search_multitransportтолько чтениеидемпотентныйвнешний мир

One-call multimodal 'how to get there' — runs avia + railway + bus + etrain in parallel and returns a unified sorted list under variants[]. PRICE BASIS — the modes do NOT agree on what a price covers: avia and bus quote the whole party, rail and etrain quote ONE SEAT (upstream never scales a train fare by the party). So with adults > 1 every variant also carries price_party — the comparable total for the searched party — and that is what the price ranking, meta.modes_summary.<mode>.min_price_party and any cross-mode comparison must use; min_price and price stay each product's own number. meta.pricing says which basis is in force. Comparing raw price across modes at adults > 1 makes the train look ~N times cheaper than it is. The rail price_party is «от»: it assumes the party fits one fare row. NB rail can also answer OUTSIDE that list: on a route with no direct train, meta.modes_summary.railway.interchange_routes holds transfer PLANS (legs may be train, coach or flight) while variants[] stays empty for that mode — they are route suggestions, not bookable variants (no single ticket, cart or link for the whole trip), so they are not ranked among the others. Do NOT read an empty rail entry as «поездов нет» without checking that block; the same filter counters ride along (interchange_routes_dropped_over_cap / ..._wrong_carrier / ..._wrong_seat_category / ..._unverified_seat_category), and a non-zero one next to an empty block means «есть, но не проходят фильтр», not «нет». Shape and booking rules are identical to search_rail's meta.interchange_routes — see get_rail_instructions. meta.modes_summary.<mode>.searched appears when that mode resolved a side to a DIFFERENT place than meta.from / meta.to — it happens when the wording names an airport, whose row and its city's row carry different geo_ids. Say it out loud when present: those offers depart from the place named there, not from the page's. meta.modes_summary.<mode>.cashback carries that mode's Tutu loyalty rate (per mode — the rate differs by product): bonus points credited to the buyer's Tutu account after payment, NOT money off price, never to be presented as a discount. Absent for a mode that earns none. It is tagged scope: "mode_page": the rate and the rows_at_rate / rows_total counters describe that mode's own FETCHED page (same population as the count / min_price beside them) — neither the merged page nor everything the mode matched, so never call that coverage exhaustive. exceptions[] is narrowed to offers on the merged page, and the full per-fare breakdown comes from that mode's own search_*. Each entry has the same shape as a single-mode search_* offer in the default compact view: price, legs[].segments[], search_results_url, checkout_ref, and details_ref for rail/bus where available — no ready checkout link (etrain's schedule checkout_url is the one exception). Fare families are nested but mode-dependent (same contract as the single-mode tools): avia/bus/etrain entries carry a per-fare variants[] (each with its own offer_hash); RAIL entries instead carry a fares summary {count, price_from, price_to, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?, seat_categories?, uncategorized_fares?} — there is NO rail variants[]/offer_hash to read or override. The counts say how many fare rows are KNOWN refundable / exchangeable; refundable_unknown / changeable_unknown count the rows upstream left unresolved, so while they are present a zero is «не подтверждено», not «не бывает». seat_categories maps each car category the train sells (SEDENTARY = сидячий / RESERVED_SEAT = плацкарт / COMPARTMENT = купе / LUX = СВ / SOFT / SHARED) to {count, price_from}, so «сидячий или купе, и почём» is answerable right here. A category absent from it is NOT on sale — unless uncategorized_fares is present, which counts fare rows upstream left unclassified, and then absence proves nothing. Class NAMES and amenities come from get_offer_details(product_type='rail') and nowhere else — view='full' restores the per-fare rows (price, conditions, service_class code, seat_category, seats_left), not the class cards. To search ONLY some categories use single-mode search_rail(seat_categories=[...]). Use optimize_for='price'|'time' to rank per mode. For checkout pass the entry's checkout_ref to create_checkout_link (etrain entries also carry a ready schedule checkout_url). Soft-fails per mode: a mode that could not answer appears in meta.unavailable[] with reason = upstream (down) / no_route / geo / error, and the rest of the result stays usable. geo means NOT SEARCHED. An unresolved or ambiguous name (or a geo index that did not answer) is described ONCE per side in meta.geo.origin / meta.geo.destination (kind, query, candidates, known_as, hint); a place that DID resolve but has no id for that mode keeps its full text in that mode's unavailable[].detail only — its hint (e.g. the avia city for a resort) is mode-specific, there is no meta.geo block for it. meta.from/to echo the place the offers are for (name, geo_id, region, plus match + query when the resolved name differs from the wording sent, and also_named[] for same-name places that lost — mention them); they come from the mode that found offers, and a mode that resolved a DIFFERENT place is demoted to unavailable with its offers, so variants[] never mixes places. When every mode refused (could not resolve … — equal candidates with regions, or a note that only Tutu's hotel index lists the place), ask the user which one or resend as '<name>, <область>'; never search another city instead.

Compare all transport modes (Tutu.ru)

Параметры
  • adultsinteger

    Number of adult passengers. Multitransport searches ADULTS ONLY — for a party with children run the concrete mode's search instead (search_avia / search_bus take children), so offers are priced for the real composition. What this count does per mode: avia and bus are PRICED for it upstream, while rail and etrain are not (a train fare is one seat whatever the party), so above 1 it is what price_party / min_price_party are computed with and what the price ranking uses.

  • carriersstring[] | null

    Keep only offers by these carriers in every mode. Case-insensitive substring on carrier display names (pass values echoed from a per-mode search's meta.carriers_available); an empty list is a no-op. All carriers on a multi-carrier offer must match one of the requested values. Applied per-mode, like price_max.

  • departure_datestring | null

    Departure date, YYYY-MM-DD.

  • destinationstring | null

    Destination city. Pass a CITY here — a specific airport (name/IATA code) only makes sense for the avia mode; use search_avia for that. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.to then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). to_city is accepted as a backward-compatible alias.

  • direct_onlyboolean

    Keep only direct offers (no transfers) in every mode. Applied per-mode, like price_max.

  • from_citystring | null

    Deprecated alias for origin.

  • modesenum[] | null

    Subset of modes to include. Default: all four.

  • optimize_forenum

    Sort variants by total price (default) or by total trip duration.

  • originstring | null

    Origin city. Pass a CITY here — a specific airport (name/IATA code) only makes sense for the avia mode; use search_avia for that. Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.from then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). from_city is accepted as a backward-compatible alias.

  • pageinteger

    1-indexed page number.

  • page_sizeinteger

    Variants per page after merging across modes.

  • price_maxnumber | null

    Hard cap on price per variant (RUB), read as a budget for the whole searched party. Applied to every mode independently and always in the party basis: avia and bus test it against their own (already whole-party) price, while rail and etrain — which quote one seat — test price_max / adults, so one cap means the same money everywhere. Compare survivors on price_party.

  • to_citystring | null

    Deprecated alias for destination.

  • viewenum

    Response detail level: compact (default) or full. compact returns lean decision cards — rail collapses the long per-class fare list to a fares summary {count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?} (the full per-class breakdown and car classes come from get_offer_details; per-variant conditions: {refundable, changeable} come with view='full'), and rail/bus drop the per-segment rating that duplicates the offer-level one. Avia and etrain are left unchanged — neither has a read-only detail endpoint to recover dropped fares/ratings from. full inlines every fare variant and per-segment review_summary. Prefer compact for first-pass search; pass full only when the user needs the whole fare breakdown inline for several offers at once.

search_railтолько чтениеидемпотентныйвнешний мир

Search Russian Railways (РЖД) tickets between two cities. Returns the real departure & arrival station names per offer (useful when a city has several stations — Москва has Курский / Ленинградский / Казанский). origin/destination take a city or a station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). meta.from/to echo the resolved place (name, geo_id, region); match + query are added only when the resolved name differs from the wording sent (lead = a short form or typo read by Tutu's own ranking — say which city came back and keep going; qualified = the trailing words acted as a region filter; prefix), and also_named[] lists same-name places that lost — mention them, never as a blocking question. An ambiguous or unknown name is REFUSED with could not resolve … (the equal candidates with regions, or a note that only Tutu's hotel index lists the place — then offer search_hotels), never searched as another city: ask the user or resend as '<name>, <область>' (name first). PRICE BASIS — every amount here is ONE SEAT: price, fares.price_from / price_to, seat_categories[].price_from and a transfer plan's price_from are one ticket, because upstream prices a train fare per passenger and never scales it by the party (unlike search_avia / search_bus, whose prices already cover everyone). meta.pricing.basis="per_seat" states this on every page; with passengers > 1 each offer additionally carries price_party (that offer's cheapest fare x passengers) and a transfer plan price_party_from. Quote price_party as «от» — it assumes the whole party travels on ONE fare row, which holds only while that row still seats them (seats_left >= N, view='full'); a mixed-class party pays more. Never present a bare price as the total for several travellers. Each offer carries: price, a fares summary in the default compact view ({count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?, seat_categories?, uncategorized_fares?} — the counts say how many fare rows are KNOWN refundable / exchangeable, so refundable_count: 0 means «none confirmed refundable», not «no refundable fare exists»; refundable_unknown / changeable_unknown count the rows upstream left unresolved on that field (absent when every row resolved it), and while they are present the zero proves nothing — say the check was inconclusive for N тарифов instead of denying. A count itself is absent when NO row resolved the field; seat_categories maps each car category present on the train to {count, price_from} — SEDENTARY (сидячий) / RESERVED_SEAT (плацкарт) / COMPARTMENT (купе) / LUX (СВ) / SOFT / SHARED (общий) — so «есть ли сидячка и почём» is answered here, without get_offer_details / get_rail_seatmap. A missing category means it is NOT on sale — unless uncategorized_fares is present, which counts fare rows upstream left unclassified, so absence proves nothing until they're resolved. It bounds the refund / exchange counts as well ONLY when you passed seat_categories: that filter deletes those rows before the summary is built, so re-run WITHOUT the filter — view='full' cannot bring them back. Unfiltered, they stay counted. Never add uncategorized_fares to the *_unknown counters; the per-class ladder купе / плацкарт / СВ with names + amenities comes from get_offer_details and nowhere else. view='full' inlines the full variants[] instead — no class cards, but each row carries conditions: {refundable, changeable} so you can tell WHICH price is the refundable one, plus seat_category, service_class (Express-3 code like 2С / 3Э), the same uncategorized_fares count at OFFER level (there is no fares block in that view) and seats_left (seats on sale for that fare row — a party of N needs seats_left >= N; do NOT sum it across rows, tariffs of one class share inventory)), legs[].segments[] with carrier + train number + train brand (vehicle_meta: name like «Ласточка» / «Сапсан» when branded, plus is_premium / is_double_decker when true — an unbranded двухэтажный still carries the flag with no name; omitted only for a plain train) + review_summary when Tutu has train feedback, search_results_url (tutu.ru listing page), and checkout_ref — the handoff object for create_checkout_link (offers deliberately carry NO ready checkout link: the link comes from that tool). details_ref can be passed to get_offer_details(product_type='rail', ...) to load service_classes[] (one card per class: description, structured amenities[] codes like WIFI / AIR_CONDITIONING / BIO_TOILET with Russian labels, rating, photos), lean fare rows in variants[] (join to the class card via class_index), cars[], and a train_vehicle block (name, is_premium, is_firm, is_double_decker, has_photo, has_tour_3d) plus a ticket block. For amenity-code localization see tutu://amenities/dictionary. Car CATEGORY (сидячий / плацкарт / купе / СВ) is answered here; this search alone does NOT answer female/male/mixed compartment, exact-seat, nearby-seat, lower/upper or WC-distance questions — for those, the next step is get_rail_seatmap(details_ref=offer.details_ref) on the chosen offer (use its task= modes for gender/far-from-WC). Paginated + sortable — and a page is a WINDOW over the matched trains (default 10 per page, cheapest-first), NOT the day's schedule: meta.total_matched is how many trains matched in total, meta.has_more says pages remain. A train absent from the current page may still run that day, so NEVER answer «такого поезда нет» from one page while has_more is true — when the user names a specific train, pass train_numbers instead of paging blind. And mind what an empty filtered result proves: in a NUMBER-ONLY search it means «билетов на этот поезд в продаже нет» — and no more: the pool lists BOOKABLE trains only (sold-out and notify-only trains are excluded upstream), so the train may still run. Say «нет в продаже на эту дату», never a timetable claim («не ходит/не существует»). With OTHER filters also active (price_max / seat_categories / carriers / direct_only) even that much doesn't follow — the named train may be dropped by one of THOSE (each names its removals in meta.post_filter_dropped_*), so re-run number-only before concluding. And even number-only, the conclusion covers DIRECT trains: on a route answered by transfer plans the meta.interchange_routes block is nondeterministic upstream (the same request may return no plans on a given call), so an absent or emptied transfer block proves nothing — say «прямых поездов с этим номером в продаже нет» and treat the transfer side as unverified. When NO train runs the whole route on that date, offers is empty and meta.interchange_routes carries transfer plans instead — two or more legs, and a leg is a train, a coach or a flight (cheapest first; meta.interchange_routes_total is how many exist). Every filter binds this block too and says what it removed: direct_only=true suppresses it entirely; price_max caps plans by their TRIP total (meta.interchange_routes_dropped_over_cap); carriers → ..._dropped_wrong_carrier; train_numbers keeps plans with the named train on ANY leg (..._dropped_wrong_train_number); seat_categories requires the category on every TRAIN leg and re-prices the plan from those fares (a coach or flight leg has no car categories, so it is not judged and keeps its own price; an all-non-train plan cannot satisfy the request and is dropped) (..._dropped_wrong_seat_category, plus ..._unverified_seat_category when a leg still holds unclassified fares). An empty block with a non-zero DROPPED count is «есть, но не проходят фильтр»; with a non-zero UNVERIFIED count it is «не удалось проверить категорию» — neither is «вариантов нет». Check this before saying «поездов нет». A plan is a ROUTE SUGGESTION, not an offer: no checkout_ref / details_ref / seat map / single link for the whole trip (all of those describe ONE ride, and Tutu itself sells a transfer as separate orders, one per leg). Read legs[].type FIRST — a plan may mix train, bus and avia legs, and the rest of the leg reads off it; say which is which when presenting. Each leg carries type, carrier, number (train or flight number; a coach has none, so carrier + departure_at are its identity), stations, times, per-leg price_from plus either seats_left (checked against the searched party — quotable) or seats_unverified: true (upstream never stated availability for that fare: price stands, seats are not promised — always the case on avia and bus legs), a per-leg seat_categories ladder on train legs (the category must be on sale on every TRAIN leg), price_party when passengers > 1 (that leg's EXACT party total; an avia leg's price_from is derived from it by division, so never multiply it back), and the hand-off its own product supports: checkout_url (a seat page) on train and bus legs, search_url plus search_avia_args on an avia leg, which has NO cart link because the itinerary names the flight and not the fare. Exactly one of search_rail_args / search_avia_args / search_bus_args per leg — pass it VERBATIM, it echoes the searched party plus any active carriers / seat_categories; it deliberately OMITS price_max, which is a whole-trip budget — sending it per leg would permit that amount on EACH leg, so add the fares you pick and keep the SUM within the cap. Plans also carry transfers[] (layover_min, changes_station when the passenger must move to another вокзал) and a price_from that is the SUM of the cheapest fare per leg (price_basis: sum_of_cheapest_fare_per_leg) — quote it as «от»; price_party_from is the sum of the legs' party totals, not price_from times the party. To book: hand over each leg's checkout_url, or re-search that leg with its own args for the full fare/seat flow — that search also returns OTHER rides, which are not this plan: the layover and total hold only for the pair in legs[], so a substitution has to be re-checked against transfers[] and its new layover restated (or refused). Never promise one ticket for the whole trip. meta.cashback states the Tutu loyalty accrual ONCE for the whole page: rate_pct plus applies_to = all_fares / most_fares / some_fares. Under all_fares every fare row earns the same rate, so it CANNOT decide which train to take — don't rank on it, mention it once at most. Under most_fares / some_fares the rate is NOT uniform and can genuinely favour one fare: rows_at_rate of rows_total fare rows says how far from universal it is (read those two, not the list length — the list may be capped), and exceptions[] names what differs — an offer_id, its price, and a variant_id when a campaign prices one fare family only (exceptions_total when that list was capped). Quote the row by its price: in the default compact view a rail card has no variants[] to resolve that id against (they are folded into fares), so re-run with view='full' for the row's conditions or category. Fare rows carry no cashback of their own, so exceptions[] is the only place that difference is visible — read it before comparing. Either way these are bonus points credited to the buyer's Tutu account after payment — they do NOT reduce price; never present them as a discount or net them off the fare. The key is absent when nothing on the page earns any. Grounding: car types, seat counts, double-decker presence and train ratings must come from this response or from get_offer_details — never invent 'обычно бывают плацкарт / купе / СВ' or substitute web facts when the field is missing. See get_rail_instructions.

Search railway tickets (Tutu.ru)

Параметры
  • carriersstring[] | null

    Keep only offers by these carriers (e.g. 'ФПК'). Pass a name from meta.carriers_available (the operators present in this result set); case-insensitive substring, an empty list is a no-op. All carriers on a multi-carrier offer must match one of the requested values. Dropped count in meta.post_filter_dropped_wrong_carrier.

  • departure_datestring | null

    Departure date, YYYY-MM-DD.

  • destinationstring | null

    Destination city or station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.to then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). to_city is accepted as a backward-compatible alias.

  • direct_onlyboolean

    Keep only direct trains (no transfers — every leg a single segment). Dropped count in meta.post_filter_dropped_not_direct.

  • from_citystring | null

    Deprecated alias for origin.

  • originstring | null

    Origin city or station name (a station resolves when Tutu's index returns the station row, e.g. «Ярославский вокзал»; otherwise city-level). Resolved via Tutu suggest: short forms («Питер», «Мск») resolve server-side — meta.from then carries match + query, plus also_named when a same-name place lost. If the tool refuses with candidates (could not resolve …), ask the user or resend as '<name>, <область>' (name first). from_city is accepted as a backward-compatible alias.

  • pageinteger

    1-indexed page number.

  • page_sizeinteger

    Offers per page.

  • passengersinteger

    Number of adult passengers. It does NOT change the fares upstream returns — a rail price is one seat either way (verified live: identical prices at 1, 2 and 3 passengers). What it does: sizes price_party on each offer, keeps transfer plans that can actually seat the party, and travels into meta.pricing + per-leg search_rail_args. Pass the real party anyway — the totals the agent quotes depend on it.

  • price_maxnumber | null

    Hard cap on price per offer (RUB). Enforced server-side.

  • seat_categoriesstring[] | null

    Keep only trains selling at least one fare in these car categories: 'SEDENTARY' (сидячий), 'RESERVED_SEAT' (плацкарт), 'COMPARTMENT' (купе), 'LUX' (СВ), 'SOFT' (мягкий), 'SHARED' (общий). Case-insensitive; any other value is REJECTED with an error (so a typo can never masquerade as «нет таких поездов»); an empty list is a no-op. Use it for «нужна сидячка» instead of paging and reading fares.seat_categories by hand. NB it NARROWS each offer to those fares: price, fares and the sort/price_max cap then describe the requested categories only, so don't read the result as «this train has no other cars» — re-run without the filter for the full ladder. Dropped count in meta.post_filter_dropped_wrong_seat_category. meta.post_filter_unverified_seat_category counts trains holding a fare upstream left unclassified — counted over the whole pool, before the price cap and the page cut, so never report an empty or thin filtered page as «нет таких поездов» while it is non-zero.

  • sortenum

    Ordering applied before pagination.

  • to_citystring | null

    Deprecated alias for destination.

  • train_numbersstring[] | null

    Keep only offers carrying one of these train numbers (e.g. '750У') — THE way to answer «возьми поезд N»: one call instead of paging through the whole day. Matches every segment's bookable number AND its display form (a through train answers to both «135С» and «136С»), ignoring case, spaces, '', leading zeros and Latin/Cyrillic lookalike letters ('750Y' finds 750У). Applied to the full day's result set (upstream has no train-number filter, so the narrowing is ours); an empty list is a no-op. Dropped count in meta.post_filter_dropped_wrong_train_number. An empty NUMBER-ONLY result means «билетов на этот поезд в продаже нет» — NOT that the train does not run: the search covers bookable trains only, so a sold-out train is absent too. Combined with other filters it proves even less — the train may be dropped by price/category/carrier (see their meta.post_filter_dropped_* counters); re-run number-only before concluding. The conclusion also covers DIRECT trains only — the transfer block (meta.interchange_routes) is nondeterministic upstream, so its absence never proves the train is not a leg of a valid transfer. And without the filter, a train missing from the current PAGE proves nothing while meta.has_more is true.

  • viewenum

    Response detail level: compact (default) or full. compact returns lean decision cards — rail collapses the long per-class fare list to a fares summary {count, price_from, price_to, currency, refundable_count?, changeable_count?, refundable_unknown?, changeable_unknown?} (the full per-class breakdown and car classes come from get_offer_details; per-variant conditions: {refundable, changeable} come with view='full'), and rail/bus drop the per-segment rating that duplicates the offer-level one. Avia and etrain are left unchanged — neither has a read-only detail endpoint to recover dropped fares/ratings from. full inlines every fare variant and per-segment review_summary. Prefer compact for first-pass search; pass full only when the user needs the whole fare breakdown inline for several offers at once.

Выберите клиент, чтобы установить Туту.ру MCP: поиск билетов:

Любой клиент

Конфиг добавления MCP сервера стандартный, обычно он не меняется вообще, поэтому подходит практически к любому ИИ клиенту, поддерживающему MCP.

{
  "mcpServers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}
CursorCursor

Перейдите по ссылке и сервер автоматически будет добавлен в Cursor. Либо откройте/создайте файл ~/.cursor/mcp.json(%USERPROFILE%\.cursor\mcp.json на Windows) и добавьте конфиг сервера:

{
  "mcpServers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}
VS Code

Создайте .vscode/mcp.json в проекте (ключ верхнего уровня — servers):

{
  "servers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}

Либо добавьте сервер через терминал:

code --add-mcp "{\"name\":\"tutu-ru-mcp\",\"type\":\"http\",\"url\":\"https://mcp.tutu.ru/mcp\"}"
ClaudeClaude Desktop

Откройте Settings → Developer → Edit Config — это откроет файл claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Впишите сервер и полностью перезапустите Claude Desktop:

{
  "mcpServers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}
Claude CodeClaude Code

Добавьте сервер одной командой в терминале:

claude mcp add --transport http tutu-ru-mcp https://mcp.tutu.ru/mcp

Либо через JSON-конфиг:

claude mcp add-json tutu-ru-mcp "{\"type\":\"http\",\"url\":\"https://mcp.tutu.ru/mcp\"}"
CodexCodex

Добавьте сервер командой в терминале:

codex mcp add tutu-ru-mcp --url https://mcp.tutu.ru/mcp

Либо вручную в ~/.codex/config.toml(%USERPROFILE%\.codex\config.toml на Windows):

[mcp_servers.tutu-ru-mcp]
url = "https://mcp.tutu.ru/mcp"
PerplexityPerplexity

MCP доступен подписчикам Perplexity Pro / Max / Enterprise. Локальные серверы — только в приложении для macOS.

  1. Откройте Настройки аккаунта → Connectors.
  2. Установите вспомогательное приложение PerplexityXPC (один раз).
  3. Нажмите Add Connector → вкладка Simple.
  4. В поле Server Name укажите Туту.ру MCP: поиск билетов.

Нажмите Save и дождитесь статуса Running.

WindsurfWindsurf

Откройте Windsurf Settings → Cascade → MCP Servers или отредактируйте файл ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}
ClineCline

В панели Cline нажмите иконку MCP Servers → Configure → Configure MCP Servers (или отредактируйте ~/.cline/mcp.json) и добавьте сервер:

{
  "mcpServers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp",
      "disabled": false,
      "autoApprove": []
    }
  }
}
Continue

Создайте файл в .continue/mcpServers/ (например tutu-ru-mcp.yaml) или добавьте блок в config.yaml. MCP работает только в режиме agent:

mcpServers:
  - name: tutu-ru-mcp
    type: http
    url: https://mcp.tutu.ru/mcp
Zed

Выполните agent: add context server или откройте настройки (zed: open settings file) и добавьте сервер в объект context_servers:

{
  "context_servers": {
    "tutu-ru-mcp": {
      "type": "http",
      "url": "https://mcp.tutu.ru/mcp"
    }
  }
}

Похожие MCP-сервера

osulivan/skill4agent-mcp-server

osulivan/skill4agent-mcp-server

от osulivan

MCP сервер для skill4agent ищет, показывает и устанавливает AI навыки прямо в диалогах. Включает поиск по ключевым словам и категориям, детальную документацию и установку. Идеален для быстрого подключения готовых skill-команд в AI-ассистентах.

TypeScript⭐ 4
RuSender MCP

RuSender MCP

официальный

Удалённый MCP-сервер RuSender: создание email-рассылок и отправка писем через AI-ассистента. Авторизация через OAuth 2.1.

shaikhspeare/wanderlog-mcp

shaikhspeare/wanderlog-mcp

от shaikhspeare

MCP-сервер для работы с Wanderlog через ИИ-агентов: создавайте и редактируйте маршруты, добавляйте места, заметки, отели и чеклисты прямо в диалоге. Поможет быстро спланировать поездку без ручного ...

TypeScript⭐ 145
ckalima/pipedrive-mcp-server

ckalima/pipedrive-mcp-server

от ckalima

MCP-сервер для интеграции Pipedrive CRM с AI-ассистентами. Позволяет запрашивать, создавать и обновлять сделки, контакты, email и другие данные прямо из диалога. Полезен менеджерам по продажам и ра...

TypeScript⭐ 9
cablate/mcp-google-map

cablate/mcp-google-map

от cablate

Ищет места на Google Maps, строит маршруты до 25 точек, показывает погоду, качество воздуха и снимки карт. 18 инструментов, больше, чем у Google Grounding.

TypeScript⭐ 466
ymw0407/auth-fetch-mcp

ymw0407/auth-fetch-mcp

от ymw0407

MCP сервер для AI-ассистентов: открывает браузер для входа на защищённые страницы, захватывает очищенный HTML и сохраняет сессии локально. Помогает разработчикам читать сайты с авторизацией через ИИ без повторных логинов.

TypeScript⭐ 36
© Каталог MCP, 2026. Все права защищены.
Проект не аффилирован с Anthropic и любыми упомянутыми продуктами.
Все названия и торговые марки принадлежат их владельцам.
Контакты для связи: hi@mcp-katalog.ru

Лука Никитин