get-tickets
Retrieve available train ticket information from 12306 using specific station codes and travel dates. Filter results by train types for accurate search results.
Instructions
查询12306余票信息。
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | 查询日期,格式为 "yyyy-MM-dd"。如果用户提供的是相对日期(如“明天”),请务必先调用 `get-current-date` 接口获取当前日期,并计算出目标日期。 | |
| fromStation | Yes | 出发地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。 | |
| toStation | Yes | 到达地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。 | |
| trainFilterFlags | No | 车次筛选条件,默认为空,即不筛选。例如用户说“高铁票”,则应使用 "G"。可选标志:[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)] |
Implementation Reference
- src/index.ts:714-779 (handler)The handler function for the 'get-tickets' tool. It validates inputs, fetches ticket data from 12306 API using cookies, parses the raw data into structured TicketInfo using helper functions, applies train filters, and returns formatted text output.async ({ date, fromStation, toStation, trainFilterFlags }) => { // 检查日期是否早于当前日期 if (!checkDate(date)) { return { content: [ { type: 'text', text: 'Error: The date cannot be earlier than today.', }, ], }; } if ( !Object.keys(STATIONS).includes(fromStation) || !Object.keys(STATIONS).includes(toStation) ) { return { content: [{ type: 'text', text: 'Error: Station not found. ' }], }; } const queryParams = new URLSearchParams({ 'leftTicketDTO.train_date': date, 'leftTicketDTO.from_station': fromStation, 'leftTicketDTO.to_station': toStation, purpose_codes: 'ADULT', }); const queryUrl = `${API_BASE}/otn/leftTicket/query`; const cookies = await getCookie(API_BASE); if (cookies == null) { return { content: [ { type: 'text', text: 'Error: get cookie failed. Check your network.', }, ], }; } const queryResponse = await make12306Request<LeftTicketsQueryResponse>( queryUrl, queryParams, { Cookie: formatCookies(cookies) } ); if (queryResponse === null || queryResponse === undefined) { return { content: [{ type: 'text', text: 'Error: get tickets data failed. ' }], }; } const ticketsData = parseTicketsData(queryResponse.data.result); let ticketsInfo: TicketInfo[]; try { ticketsInfo = parseTicketsInfo(ticketsData, queryResponse.data.map); } catch (error) { console.error('Error: parse tickets info failed. ',error); return { content: [{ type: 'text', text: 'Error: parse tickets info failed. ' }], }; } const filteredTicketsInfo = filterTicketsInfo<TicketInfo>( ticketsInfo, trainFilterFlags ); return { content: [{ type: 'text', text: formatTicketsInfo(filteredTicketsInfo) }], }; }
- src/index.ts:687-713 (schema)Zod input schema defining parameters for the 'get-tickets' tool: date (yyyy-MM-dd), fromStation and toStation codes, optional trainFilterFlags.{ date: z .string() .length(10) .describe( '查询日期,格式为 "yyyy-MM-dd"。如果用户提供的是相对日期(如“明天”),请务必先调用 `get-current-date` 接口获取当前日期,并计算出目标日期。' ), fromStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。' ), toStation: z .string() .describe( '到达地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。' ), trainFilterFlags: z .string() .regex(/^[GDZTKOFS]*$/) .max(8) .optional() .default('') .describe( '车次筛选条件,默认为空,即不筛选。例如用户说“高铁票”,则应使用 "G"。可选标志:[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)]' ), },
- src/index.ts:684-780 (registration)Registers the 'get-tickets' tool with the MCP server, including name, description, input schema, and handler function.server.tool( 'get-tickets', '查询12306余票信息。', { date: z .string() .length(10) .describe( '查询日期,格式为 "yyyy-MM-dd"。如果用户提供的是相对日期(如“明天”),请务必先调用 `get-current-date` 接口获取当前日期,并计算出目标日期。' ), fromStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。' ), toStation: z .string() .describe( '到达地的 `station_code` 。必须是通过 `get-station-code-by-name` 或 `get-station-code-of-city` 接口查询得到的编码,严禁直接使用中文地名。' ), trainFilterFlags: z .string() .regex(/^[GDZTKOFS]*$/) .max(8) .optional() .default('') .describe( '车次筛选条件,默认为空,即不筛选。例如用户说“高铁票”,则应使用 "G"。可选标志:[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)]' ), }, async ({ date, fromStation, toStation, trainFilterFlags }) => { // 检查日期是否早于当前日期 if (!checkDate(date)) { return { content: [ { type: 'text', text: 'Error: The date cannot be earlier than today.', }, ], }; } if ( !Object.keys(STATIONS).includes(fromStation) || !Object.keys(STATIONS).includes(toStation) ) { return { content: [{ type: 'text', text: 'Error: Station not found. ' }], }; } const queryParams = new URLSearchParams({ 'leftTicketDTO.train_date': date, 'leftTicketDTO.from_station': fromStation, 'leftTicketDTO.to_station': toStation, purpose_codes: 'ADULT', }); const queryUrl = `${API_BASE}/otn/leftTicket/query`; const cookies = await getCookie(API_BASE); if (cookies == null) { return { content: [ { type: 'text', text: 'Error: get cookie failed. Check your network.', }, ], }; } const queryResponse = await make12306Request<LeftTicketsQueryResponse>( queryUrl, queryParams, { Cookie: formatCookies(cookies) } ); if (queryResponse === null || queryResponse === undefined) { return { content: [{ type: 'text', text: 'Error: get tickets data failed. ' }], }; } const ticketsData = parseTicketsData(queryResponse.data.result); let ticketsInfo: TicketInfo[]; try { ticketsInfo = parseTicketsInfo(ticketsData, queryResponse.data.map); } catch (error) { console.error('Error: parse tickets info failed. ',error); return { content: [{ type: 'text', text: 'Error: parse tickets info failed. ' }], }; } const filteredTicketsInfo = filterTicketsInfo<TicketInfo>( ticketsInfo, trainFilterFlags ); return { content: [{ type: 'text', text: formatTicketsInfo(filteredTicketsInfo) }], }; } );
- src/index.ts:267-291 (helper)Helper function to parse raw TicketData into user-friendly TicketInfo, including station names from map, prices extraction, and DW flags.function parseTicketsInfo(ticketsData: TicketData[], map:Record<string,string>): TicketInfo[] { const result: TicketInfo[] = []; for (const ticket of ticketsData) { const prices = extractPrices( ticket.yp_info_new, ticket.seat_discount_info, ticket ); const dw_flag = extractDWFlags(ticket.dw_flag); result.push({ train_no: ticket.train_no, start_train_code: ticket.station_train_code, start_time: ticket.start_time, arrive_time: ticket.arrive_time, lishi: ticket.lishi, from_station: map[ticket.from_station_telecode], to_station: map[ticket.to_station_telecode], from_station_telecode: ticket.from_station_telecode, to_station_telecode: ticket.to_station_telecode, prices: prices, dw_flag: dw_flag, }); } return result; }
- src/index.ts:293-309 (helper)Helper function to format TicketInfo array into a readable markdown-like string for output.function formatTicketsInfo(ticketsInfo: TicketInfo[]): string { if (ticketsInfo.length === 0) { return '没有查询到相关车次信息'; } let result = '车次 | 出发站 -> 到达站 | 出发时间 -> 到达时间 | 历时\n'; ticketsInfo.forEach((ticketInfo) => { let infoStr = ''; infoStr += `${ticketInfo.start_train_code}(实际车次train_no: ${ticketInfo.train_no}) ${ticketInfo.from_station}(telecode: ${ticketInfo.from_station_telecode}) -> ${ticketInfo.to_station}(telecode: ${ticketInfo.to_station_telecode}) ${ticketInfo.start_time} -> ${ticketInfo.arrive_time} 历时:${ticketInfo.lishi}`; ticketInfo.prices.forEach((price) => { infoStr += `\n- ${price.seat_name}: ${ price.num.match(/^\d+$/) ? price.num + '张' : price.num }剩余 ${price.price}元`; }); result += `${infoStr}\n`; }); return result; }