get-interline-tickets
Query interline ticket availability on China's 12306 railway system for connecting journeys between stations, with filtering options for train types and schedules.
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-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。 | |
| toStation | Yes | 出发地的 `station_code` 。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。 | |
| middleStation | No | 中转地的 `station_code` ,可选。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。 | |
| showWZ | No | 是否显示无座车,默认不显示无座车。 | |
| trainFilterFlags | No | 车次筛选条件,默认为空。从以下标志中选取多个条件组合[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)] | |
| earliestStartTime | No | 最早出发时间(0-24),默认为0。 | |
| latestStartTime | No | 最迟出发时间(0-24),默认为24。 | |
| sortFlag | No | 排序方式,默认为空,即不排序。仅支持单一标识。可选标志:[startTime(出发时间从早到晚), arriveTime(抵达时间从早到晚), duration(历时从短到长)] | |
| sortReverse | No | 是否逆向排序结果,默认为false。仅在设置了sortFlag时生效。 | |
| limitedNum | No | 返回的中转余票数量限制,默认为10。 | |
| format | No | 返回结果格式,默认为text,建议使用text。可选标志:[text, json] | text |
Implementation Reference
- src/index.ts:1230-1367 (handler)The core handler function for the 'get-interline-tickets' tool. It validates inputs, fetches data from 12306 interline API using cookies, paginates results, parses raw data into structured InterlineInfo using helpers, applies filters and sorting, and returns formatted text or JSON.async ({ date, fromStation, toStation, middleStation, showWZ, trainFilterFlags, earliestStartTime, latestStartTime, sortFlag, sortReverse, limitedNum, format, }) => { // 检查日期是否早于当前日期 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 queryUrl = `${API_BASE}${LCQUERY_PATH}`; const cookies = await getCookie(); if (cookies == null || Object.entries(cookies).length === 0) { return { content: [ { type: 'text', text: 'Error: get cookie failed. Check your network.', }, ], }; } var interlineData: InterlineData[] = []; const queryParams = new URLSearchParams({ train_date: date, from_station_telecode: fromStation, to_station_telecode: toStation, middle_station: middleStation, result_index: '0', can_query: 'Y', isShowWZ: showWZ ? 'Y' : 'N', purpose_codes: '00', // 00: 成人票 0X: 学生票 channel: 'E', // 没搞清楚什么用 }); while (interlineData.length < limitedNum) { const queryResponse = await make12306Request<InterlineQueryResponse>( queryUrl, queryParams, { Cookie: formatCookies(cookies) } ); // 处理请求错误 if (queryResponse === null || queryResponse === undefined) { return { content: [ { type: 'text', text: 'Error: request interline tickets data failed. ', }, ], }; } // 请求成功,但查询有误 if (typeof queryResponse.data == 'string') { return { content: [ { type: 'text', text: `很抱歉,未查到相关的列车余票。(${queryResponse.errorMsg})`, }, ], }; } interlineData = interlineData.concat(queryResponse.data.middleList); if (queryResponse.data.can_query == 'N') { break; } queryParams.set( 'result_index', queryResponse.data.result_index.toString() ); } // 请求和查询都没问题 let interlineTicketsInfo: InterlineInfo[]; try { interlineTicketsInfo = parseInterlinesInfo(interlineData); } catch (error) { return { content: [ { type: 'text', text: `Error: parse tickets info failed. ${error}`, }, ], }; } const filteredInterlineTicketsInfo = filterTicketsInfo<InterlineInfo>( interlineTicketsInfo, trainFilterFlags, earliestStartTime, latestStartTime, sortFlag, sortReverse, limitedNum ); var formatedResult; switch (format) { case 'json': formatedResult = JSON.stringify(filteredInterlineTicketsInfo); break; default: formatedResult = formatInterlinesInfo( filteredInterlineTicketsInfo ); break; } return { content: [ { type: 'text', text: formatedResult, }, ], };
- src/index.ts:1149-1229 (schema)Zod schema defining the input parameters for the 'get-interline-tickets' tool, including date, stations, filters, sorting options, and output format.{ date: z .string() .length(10) .describe( '查询日期,格式为 "yyyy-MM-dd"。如果用户提供的是相对日期(如“明天”),请务必先调用 `get-current-date` 接口获取当前日期,并计算出目标日期。' ), fromStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), toStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), middleStation: z .string() .optional() .default('') .describe( '中转地的 `station_code` ,可选。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), showWZ: z .boolean() .optional() .default(false) .describe('是否显示无座车,默认不显示无座车。'), trainFilterFlags: z .string() .regex(/^[GDZTKOFS]*$/) .max(8) .optional() .default('') .describe( '车次筛选条件,默认为空。从以下标志中选取多个条件组合[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)]' ), earliestStartTime: z .number() .min(0) .max(24) .optional() .default(0) .describe('最早出发时间(0-24),默认为0。'), latestStartTime: z .number() .min(0) .max(24) .optional() .default(24) .describe('最迟出发时间(0-24),默认为24。'), sortFlag: z .string() .optional() .default('') .describe( '排序方式,默认为空,即不排序。仅支持单一标识。可选标志:[startTime(出发时间从早到晚), arriveTime(抵达时间从早到晚), duration(历时从短到长)]' ), sortReverse: z .boolean() .optional() .default(false) .describe( '是否逆向排序结果,默认为false。仅在设置了sortFlag时生效。' ), limitedNum: z .number() .min(1) .optional() .default(10) .describe('返回的中转余票数量限制,默认为10。'), format: z .string() .regex(/^(text|json)$/i) .default('text') .optional() .describe( '返回结果格式,默认为text,建议使用text。可选标志:[text, json]' ), },
- src/index.ts:1146-1369 (registration)Registration of the 'get-interline-tickets' tool on the MCP server, including name, description, input schema, and handler function.server.tool( 'get-interline-tickets', '查询12306中转余票信息。尚且只支持查询前十条。', { date: z .string() .length(10) .describe( '查询日期,格式为 "yyyy-MM-dd"。如果用户提供的是相对日期(如“明天”),请务必先调用 `get-current-date` 接口获取当前日期,并计算出目标日期。' ), fromStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), toStation: z .string() .describe( '出发地的 `station_code` 。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), middleStation: z .string() .optional() .default('') .describe( '中转地的 `station_code` ,可选。必须是通过 `get-station-code-by-names` 或 `get-station-code-of-citys` 接口查询得到的编码,严禁直接使用中文地名。' ), showWZ: z .boolean() .optional() .default(false) .describe('是否显示无座车,默认不显示无座车。'), trainFilterFlags: z .string() .regex(/^[GDZTKOFS]*$/) .max(8) .optional() .default('') .describe( '车次筛选条件,默认为空。从以下标志中选取多个条件组合[G(高铁/城际),D(动车),Z(直达特快),T(特快),K(快速),O(其他),F(复兴号),S(智能动车组)]' ), earliestStartTime: z .number() .min(0) .max(24) .optional() .default(0) .describe('最早出发时间(0-24),默认为0。'), latestStartTime: z .number() .min(0) .max(24) .optional() .default(24) .describe('最迟出发时间(0-24),默认为24。'), sortFlag: z .string() .optional() .default('') .describe( '排序方式,默认为空,即不排序。仅支持单一标识。可选标志:[startTime(出发时间从早到晚), arriveTime(抵达时间从早到晚), duration(历时从短到长)]' ), sortReverse: z .boolean() .optional() .default(false) .describe( '是否逆向排序结果,默认为false。仅在设置了sortFlag时生效。' ), limitedNum: z .number() .min(1) .optional() .default(10) .describe('返回的中转余票数量限制,默认为10。'), format: z .string() .regex(/^(text|json)$/i) .default('text') .optional() .describe( '返回结果格式,默认为text,建议使用text。可选标志:[text, json]' ), }, async ({ date, fromStation, toStation, middleStation, showWZ, trainFilterFlags, earliestStartTime, latestStartTime, sortFlag, sortReverse, limitedNum, format, }) => { // 检查日期是否早于当前日期 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 queryUrl = `${API_BASE}${LCQUERY_PATH}`; const cookies = await getCookie(); if (cookies == null || Object.entries(cookies).length === 0) { return { content: [ { type: 'text', text: 'Error: get cookie failed. Check your network.', }, ], }; } var interlineData: InterlineData[] = []; const queryParams = new URLSearchParams({ train_date: date, from_station_telecode: fromStation, to_station_telecode: toStation, middle_station: middleStation, result_index: '0', can_query: 'Y', isShowWZ: showWZ ? 'Y' : 'N', purpose_codes: '00', // 00: 成人票 0X: 学生票 channel: 'E', // 没搞清楚什么用 }); while (interlineData.length < limitedNum) { const queryResponse = await make12306Request<InterlineQueryResponse>( queryUrl, queryParams, { Cookie: formatCookies(cookies) } ); // 处理请求错误 if (queryResponse === null || queryResponse === undefined) { return { content: [ { type: 'text', text: 'Error: request interline tickets data failed. ', }, ], }; } // 请求成功,但查询有误 if (typeof queryResponse.data == 'string') { return { content: [ { type: 'text', text: `很抱歉,未查到相关的列车余票。(${queryResponse.errorMsg})`, }, ], }; } interlineData = interlineData.concat(queryResponse.data.middleList); if (queryResponse.data.can_query == 'N') { break; } queryParams.set( 'result_index', queryResponse.data.result_index.toString() ); } // 请求和查询都没问题 let interlineTicketsInfo: InterlineInfo[]; try { interlineTicketsInfo = parseInterlinesInfo(interlineData); } catch (error) { return { content: [ { type: 'text', text: `Error: parse tickets info failed. ${error}`, }, ], }; } const filteredInterlineTicketsInfo = filterTicketsInfo<InterlineInfo>( interlineTicketsInfo, trainFilterFlags, earliestStartTime, latestStartTime, sortFlag, sortReverse, limitedNum ); var formatedResult; switch (format) { case 'json': formatedResult = JSON.stringify(filteredInterlineTicketsInfo); break; default: formatedResult = formatInterlinesInfo( filteredInterlineTicketsInfo ); break; } return { content: [ { type: 'text', text: formatedResult, }, ], }; } );
- src/index.ts:480-531 (helper)Generic helper function to filter, sort, and limit ticket or interline ticket results based on train flags, start times, sort criteria.function filterTicketsInfo<T extends TicketInfo | InterlineInfo>( ticketsInfo: T[], trainFilterFlags: string, earliestStartTime: number = 0, latestStartTime: number = 24, sortFlag: string = '', sortReverse: boolean = false, limitedNum: number = 0 ): T[] { let result: T[]; // FilterFlags过滤 if (trainFilterFlags.length === 0) { result = ticketsInfo; } else { result = []; for (const ticketInfo of ticketsInfo) { for (const filter of trainFilterFlags) { if ( TRAIN_FILTERS[filter as keyof typeof TRAIN_FILTERS]( ticketInfo ) ) { result.push(ticketInfo); break; } } } } // startTime 过滤 result = result.filter((ticketInfo) => { const startTimeHour = parseInt(ticketInfo.start_time.split(':')[0], 10); if ( startTimeHour >= earliestStartTime && startTimeHour < latestStartTime ) { return true; } return false; }); // sort排序 if (Object.keys(TIME_COMPARETOR).includes(sortFlag)) { result.sort(TIME_COMPARETOR[sortFlag as keyof typeof TIME_COMPARETOR]); if (sortReverse) { result.reverse(); } } if (limitedNum == 0) { return result; } return result.slice(0, limitedNum); }
- src/index.ts:583-611 (helper)Helper to parse raw API interline data into structured InterlineInfo objects, calling sub-parsers for tickets.function parseInterlinesInfo(interlineData: InterlineData[]): InterlineInfo[] { const result: InterlineInfo[] = []; for (const ticket of interlineData) { const interlineTickets = parseInterlinesTicketInfo(ticket.fullList); const lishi = extractLishi(ticket.all_lishi); result.push({ lishi: lishi, start_time: ticket.start_time, start_date: ticket.train_date, middle_date: ticket.middle_date, arrive_date: ticket.arrive_date, arrive_time: ticket.arrive_time, from_station_code: ticket.from_station_code, from_station_name: ticket.from_station_name, middle_station_code: ticket.middle_station_code, middle_station_name: ticket.middle_station_name, end_station_code: ticket.end_station_code, end_station_name: ticket.end_station_name, start_train_code: interlineTickets[0].start_train_code, first_train_no: ticket.first_train_no, second_train_no: ticket.second_train_no, train_count: ticket.train_count, ticketList: interlineTickets, same_station: ticket.same_station == '0' ? true : false, same_train: ticket.same_train == 'Y' ? true : false, wait_time: ticket.wait_time, }); } return result;
- src/index.ts:614-633 (helper)Helper to format the parsed and filtered interline tickets into a human-readable text string.function formatInterlinesInfo(interlinesInfo: InterlineInfo[]): string { let result = '出发时间 -> 到达时间 | 出发车站 -> 中转车站 -> 到达车站 | 换乘标志 |换乘等待时间| 总历时\n\n'; interlinesInfo.forEach((interlineInfo) => { result += `${interlineInfo.start_date} ${interlineInfo.start_time} -> ${interlineInfo.arrive_date} ${interlineInfo.arrive_time} | `; result += `${interlineInfo.from_station_name} -> ${interlineInfo.middle_station_name} -> ${interlineInfo.end_station_name} | `; result += `${ interlineInfo.same_train ? '同车换乘' : interlineInfo.same_station ? '同站换乘' : '换站换乘' } | ${interlineInfo.wait_time} | ${interlineInfo.lishi}\n\n`; result += '\t' + formatTicketsInfo(interlineInfo.ticketList).replace(/\n/g, '\n\t'); result += '\n'; }); return result; }
- src/types.ts:202-307 (schema)TypeScript type definitions for data structures used in interline tickets processing: InterlineData, InterlineInfo, InterlineTicketData.export type InterlineData = { all_lishi: string; all_lishi_minutes: number; arrive_date: string; arrive_time: string; end_station_code: string; end_station_name: string; first_train_no: string; from_station_code: string; from_station_name: string; fullList: InterlineTicketData[]; isHeatTrain: string; isOutStation: string; lCWaitTime: string; lishi_flag: string; middle_date: string; middle_station_code: string; middle_station_name: string; same_station: string; same_train: string; score: number; score_str: string; scretstr: string; second_train_no: string; start_time: string; train_count: number; train_date: string; // 出发时间 use_time: string; wait_time: string; wait_time_minutes: number; }; export type InterlineInfo = { lishi: string; //all_lishi_minutes: number; start_time: string; start_date: string; middle_date: string; arrive_date: string; arrive_time: string; from_station_code: string; from_station_name: string; middle_station_code: string; middle_station_name: string; end_station_code: string; end_station_name: string; start_train_code: string; // 用于过滤 first_train_no: string; second_train_no: string; train_count: number; ticketList: TicketInfo[]; //isHeatTrain: string; //isOutStation: string; //lCWaitTime: string; //lishi_flag: string; same_station: boolean; same_train: boolean; wait_time: string; //wait_time_minutes: number; }; export type InterlineTicketData = { arrive_time: string; bed_level_info: string; controlled_train_flag: string; country_flag: string; day_difference: string; dw_flag: string; end_station_name: string; end_station_telecode: string; from_station_name: string; from_station_no: string; from_station_telecode: string; gg_num: string; gr_num: string; is_support_card: string; lishi: string; local_arrive_time: string; local_start_time: string; qt_num: string; rw_num: string; rz_num: string; seat_discount_info: string; seat_types: string; srrb_num: string; start_station_name: string; start_station_telecode: string; start_time: string; start_train_date: string; station_train_code: string; swz_num: string; to_station_name: string; to_station_no: string; to_station_telecode: string; train_no: string; train_seat_feature: string; trms_train_flag: string; tz_num: string; wz_num: string; yb_num: string; yp_info: string; yw_num: string; yz_num: string; ze_num: string; zy_num: string; };