search_travel_vacancy
Find available hotel rooms on Rakuten Travel using coordinates or hotel number, with filters for dates, price, and number of guests.
Instructions
Search for available hotel rooms on Rakuten Travel by location, date, and price. Requires coordinates (lat/lng) or a hotel number for location.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| checkinDate | Yes | Check-in date (YYYY-MM-DD) | |
| checkoutDate | Yes | Check-out date (YYYY-MM-DD) | |
| latitude | No | Latitude (WGS84 decimal degrees, e.g., 35.6812) | |
| longitude | No | Longitude (WGS84 decimal degrees, e.g., 139.7671) | |
| searchRadius | No | Search radius in km (0.1-3, requires lat/lng) | |
| hotelNo | No | Specific Rakuten hotel number (alternative to coordinates) | |
| maxCharge | No | Maximum price per night in yen | |
| adultNum | No | Number of adults (1-10) | |
| hits | No | Number of results |
Implementation Reference
- src/index.ts:343-405 (handler)The async handler function that executes the search_travel_vacancy tool logic: validates location input (coordinates or hotelNo), calls the Rakuten Travel VacantHotelSearch API, and maps the response into a simplified hotel result list.
async ({ checkinDate, checkoutDate, latitude, longitude, searchRadius, hotelNo, maxCharge, adultNum, hits }) => { const hasCoords = latitude !== undefined && longitude !== undefined; if (!hasCoords && hotelNo === undefined) { return { content: [ { type: "text", text: "Error: A location is required. Provide either latitude+longitude or hotelNo.", }, ], }; } if (hasCoords && hotelNo !== undefined) { return { content: [ { type: "text", text: "Error: Provide either latitude+longitude or hotelNo, not both.", }, ], }; } const params: Record<string, string> = { checkinDate, checkoutDate, adultNum: String(adultNum), hits: String(hits), }; if (hasCoords) { params.latitude = String(latitude); params.longitude = String(longitude); params.datumType = "1"; // WGS84 decimal degrees } if (hasCoords && searchRadius !== undefined) params.searchRadius = String(searchRadius); if (hotelNo !== undefined) params.hotelNo = String(hotelNo); if (maxCharge !== undefined) params.maxCharge = String(maxCharge); const data = (await rakutenRequest( ENDPOINTS.travelVacantHotelSearch, params )) as { hotels?: Array<{ hotel: Array<{ hotelBasicInfo?: Record<string, unknown>; roomInfo?: Array<{ roomBasicInfo?: Record<string, unknown>; dailyCharge?: Record<string, unknown> }> }> }> }; const hotels = data.hotels?.map((h) => { const basic = h.hotel.find((entry) => entry.hotelBasicInfo)?.hotelBasicInfo ?? {}; const room = h.hotel.find((entry) => entry.roomInfo)?.roomInfo?.[0]; return { name: basic.hotelName, address: `${basic.address1 ?? ""}${basic.address2 ?? ""}`, price: room?.dailyCharge?.total ?? basic.hotelMinCharge, rating: basic.reviewAverage, url: basic.hotelInformationUrl, imageUrl: basic.hotelImageUrl, roomName: room?.roomBasicInfo?.roomName, }; }) ?? []; return { content: [{ type: "text", text: JSON.stringify(hotels, null, 2) }], }; } ); - src/index.ts:326-342 (schema)Input schema (Zod) definition for the search_travel_vacancy tool: checkinDate, checkoutDate, optional latitude/longitude/searchRadius, optional hotelNo, optional maxCharge, adultNum (default 1), and hits (default 10).
"Search for available hotel rooms on Rakuten Travel by location, date, and price. Requires coordinates (lat/lng) or a hotel number for location.", { checkinDate: z.string().describe("Check-in date (YYYY-MM-DD)"), checkoutDate: z.string().describe("Check-out date (YYYY-MM-DD)"), latitude: z.number().optional().describe("Latitude (WGS84 decimal degrees, e.g., 35.6812)"), longitude: z.number().optional().describe("Longitude (WGS84 decimal degrees, e.g., 139.7671)"), searchRadius: z .number() .min(0.1) .max(3) .optional() .describe("Search radius in km (0.1-3, requires lat/lng)"), hotelNo: z.number().optional().describe("Specific Rakuten hotel number (alternative to coordinates)"), maxCharge: z.number().optional().describe("Maximum price per night in yen"), adultNum: z.number().min(1).max(10).default(1).describe("Number of adults (1-10)"), hits: z.number().min(1).max(30).default(10).describe("Number of results"), }, - src/index.ts:324-405 (registration)Registration of the tool via server.tool('search_travel_vacancy', ...) on the MCP server instance.
server.tool( "search_travel_vacancy", "Search for available hotel rooms on Rakuten Travel by location, date, and price. Requires coordinates (lat/lng) or a hotel number for location.", { checkinDate: z.string().describe("Check-in date (YYYY-MM-DD)"), checkoutDate: z.string().describe("Check-out date (YYYY-MM-DD)"), latitude: z.number().optional().describe("Latitude (WGS84 decimal degrees, e.g., 35.6812)"), longitude: z.number().optional().describe("Longitude (WGS84 decimal degrees, e.g., 139.7671)"), searchRadius: z .number() .min(0.1) .max(3) .optional() .describe("Search radius in km (0.1-3, requires lat/lng)"), hotelNo: z.number().optional().describe("Specific Rakuten hotel number (alternative to coordinates)"), maxCharge: z.number().optional().describe("Maximum price per night in yen"), adultNum: z.number().min(1).max(10).default(1).describe("Number of adults (1-10)"), hits: z.number().min(1).max(30).default(10).describe("Number of results"), }, async ({ checkinDate, checkoutDate, latitude, longitude, searchRadius, hotelNo, maxCharge, adultNum, hits }) => { const hasCoords = latitude !== undefined && longitude !== undefined; if (!hasCoords && hotelNo === undefined) { return { content: [ { type: "text", text: "Error: A location is required. Provide either latitude+longitude or hotelNo.", }, ], }; } if (hasCoords && hotelNo !== undefined) { return { content: [ { type: "text", text: "Error: Provide either latitude+longitude or hotelNo, not both.", }, ], }; } const params: Record<string, string> = { checkinDate, checkoutDate, adultNum: String(adultNum), hits: String(hits), }; if (hasCoords) { params.latitude = String(latitude); params.longitude = String(longitude); params.datumType = "1"; // WGS84 decimal degrees } if (hasCoords && searchRadius !== undefined) params.searchRadius = String(searchRadius); if (hotelNo !== undefined) params.hotelNo = String(hotelNo); if (maxCharge !== undefined) params.maxCharge = String(maxCharge); const data = (await rakutenRequest( ENDPOINTS.travelVacantHotelSearch, params )) as { hotels?: Array<{ hotel: Array<{ hotelBasicInfo?: Record<string, unknown>; roomInfo?: Array<{ roomBasicInfo?: Record<string, unknown>; dailyCharge?: Record<string, unknown> }> }> }> }; const hotels = data.hotels?.map((h) => { const basic = h.hotel.find((entry) => entry.hotelBasicInfo)?.hotelBasicInfo ?? {}; const room = h.hotel.find((entry) => entry.roomInfo)?.roomInfo?.[0]; return { name: basic.hotelName, address: `${basic.address1 ?? ""}${basic.address2 ?? ""}`, price: room?.dailyCharge?.total ?? basic.hotelMinCharge, rating: basic.reviewAverage, url: basic.hotelInformationUrl, imageUrl: basic.hotelImageUrl, roomName: room?.roomBasicInfo?.roomName, }; }) ?? []; return { content: [{ type: "text", text: JSON.stringify(hotels, null, 2) }], }; } ); - src/index.ts:49-83 (helper)The rakutenRequest helper function used by the tool to make authenticated API calls to Rakuten endpoints.
async function rakutenRequest( endpointUrl: string, params: Record<string, string> = {} ): Promise<unknown> { const appId = getAppId(); const accessKey = getAccessKey(); const origin = getOrigin(); const searchParams = new URLSearchParams({ applicationId: appId, accessKey, format: "json", ...params, }); const url = `${endpointUrl}?${searchParams}`; const res = await fetch(url, { headers: { Origin: origin, Referer: origin, }, }); if (!res.ok) { const status = res.status; const body = await res.text(); throw new Error(`Rakuten API error (HTTP ${status}) on ${endpointUrl}: ${body.slice(0, 200)}`); } const text = await res.text(); if (!text) return { success: true }; try { return JSON.parse(text); } catch { throw new Error(`Rakuten API returned malformed JSON on ${endpointUrl}`); } }