资讯动态

通义灵码2.5+qwen3实战:用12306-MCP打造节假日火车票智能查询助手,TaoToken统一Key接入

发布时间:2026/10/18 21:55:46 来源:尧图企业网站定制
1. 节假日抢票场景下的真实痛点与 12306-MCP 能做什么节假日抢票这件事最折磨人的不是手速而是信息差。你打开 12306 官方 App输入出发地、目的地、日期点查询翻列表看余票退出来再换一个车次组合重新查一遍。整个过程机械、重复而且一旦你想同时盯三四个备选方案手指和眼睛都不够用。更别说有些车次是整点放票、有些是分批放票你根本不可能一直守在手机前面。我试过在国庆前一周帮家里人查武汉到广州的票光是「下午 17:30 之前有哪些车次还有二等座」这个问题就在 App 里来回翻了十几分钟。后来我换了个思路能不能让程序帮我查我只负责看结果这就是 12306-MCP 要解决的问题。先说清楚三个概念方便后面跟做。12306-MCP 是什么MCP 全称 Model Context Protocol你可以把它理解成「给大模型用的标准工具接口」。12306-MCP 就是把 12306 的查询能力车站代码、余票、车次时刻封装成一组标准工具让支持 MCP 的智能体可以直接调用。它本身不卖票、不抢票只做查询所以用它来盯余票、做提醒是完全合规的。通义灵码 2.5 在这里扮演什么角色它是一个编程智能体能读 MCP 的工具描述、生成调用代码、帮你把 Node.js 服务搭起来。你不需要从零手写 HTTP 转发和 CORS 处理把需求描述清楚它给你可运行的骨架。qwen3 负责什么负责把「今天下午 17:30 之前武汉到广州的火车票」这种自然语言翻译成对 12306-MCP 工具的结构化调用参数。没有它你得自己拼 JSON有了它你说话就行。适合谁会一点 Node.js、想在节假日给自己或家人做一个余票查询小工具的人。不需要你会写 MCP 协议但需要你能跑node命令、能看懂 JSON 配置。这一篇我会交付四样东西可复制的 MCP 配置片段、Node.js 启动脚本、TaoToken 统一 Key 的接入步骤、以及本地验证查询结果的完整动作。你照着做最后能在浏览器里输入自然语言拿到车次和余票。2. TaoToken 前置准备统一 Key 接入 12306-MCP 与 qwen3在动手写代码之前先把「钥匙」准备好。这里我用 TaoToken 做统一接入原因是它把模型调用和 MCP 工具调用的鉴权收敛到一个 Key 上省得你在通义灵码、本地脚本、代理服务之间来回换 token。先明确一个边界TaoToken 在这里的角色是「统一的 API 接入层」你通过它拿到模型对话能力和工具调用能力12306-MCP 本身是独立的查询服务两者通过配置组合在一起。不要把 TaoToken 理解成某种特殊通道它就是一个标准的 API 入口。第一步拿到 API Key。访问 API Keys 管理页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite登录后创建一个新 Key复制出来。这个 Key 后面会同时用在两处一处是通义灵码里配置模型服务一处是本地 Node.js 脚本调用 qwen3。第二步确认 Base URL。TaoToken 的 API 入口是https://taotoken.net/api注意这里不加 UTM 参数保持干净。你在配置里填的 Base URL 就是它后面拼/v1/chat/completions这类路径由 SDK 或脚本自己处理。第三步确认 Model ID。qwen3 系列在 TaoToken 上的模型标识建议你先在模型对话页面确认当前可用的准确名称https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite在对话页面选择 qwen3看它实际调用的模型 ID 是什么把它记下来。常见的形式是qwen3或带版本号的变体。Model ID 必须和平台实际暴露的一致写错了会直接报模型不存在。到这里你手上有三样东西项目值用途Base URLhttps://taotoken.net/api所有请求的根地址API Key你创建的那串鉴权Model ID平台确认的 qwen3 标识指定模型第四步理解 12306-MCP 的接入位置。12306-MCP 不是通过 TaoToken 调用的它是本地或远程的一个 MCP 服务。你的架构是这样的浏览器页面 ↓ 本地 Node.js 代理proxy-server.js ↓ 12306-MCP 服务查询车次/余票 ↑ qwen3通过 TaoToken 调用负责理解自然语言并决定调哪个工具也就是说TaoToken 负责「大脑」的调用12306-MCP 负责「手脚」的执行。两者通过你的 Node.js 服务串起来。第五步环境检查。在开始写配置之前确认本机 Node.js 版本。打开终端node -v npm -v建议 Node.js 18 以上因为后面用到的fetch和部分异步写法在低版本上要额外 polyfill。如果版本太低先去 Node.js 官网装一个 LTS 版本。如果你打算长期做编码和 Agent 类项目可以了解一下 Coding Plan它更适合这种需要反复调试、多轮对话的场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite前置准备到这里就齐了。接下来进入可复制的配置环节。3. 可复制配置MCP 配置片段、Node.js 启动脚本与 settings 文件这一节是全文最核心的部分所有片段都可以直接复制。我按「MCP 配置 → 项目结构 → 启动脚本 → 模型配置」的顺序来。3.1 12306-MCP 的 MCP 配置片段在通义灵码或支持 MCP 的客户端里MCP 服务通常用一个 JSON 描述。下面是一个标准的 12306-MCP 配置片段路径和字段名保持通用写法{ mcpServers: { 12306-mcp: { command: npx, args: [ -y, 12306-mcp ], env: { MCP_LOG_LEVEL: info } } } }如果你用的是已经部署好的远程 MCP 服务把command/args换成 URL 形式{ mcpServers: { 12306-mcp: { url: http://localhost:8080/mcp, transport: http } } }注意command和url两种写法不要混用。本地用npx拉起远程用url。端口号按你实际部署的来不要照抄 8080。3.2 项目结构在本地建一个目录比如12306tray结构如下12306tray/ ├── 12306-mcp-client.js # 前端 JS 客户端逻辑 ├── index.html # 测试页面 ├── server.js # 主 HTTP 服务器 ├── proxy-server.js # 代理服务器转发到 MCP ├── check-environment.js # 环境检测 ├── start-ticket-assistant.js# 一键启动脚本 ├── config.js # 配置文件 ├── package.json └── README.md3.3 config.js把 Key 和地址集中管理// config.js module.exports { // TaoToken 统一接入 taoToken: { baseUrl: https://taotoken.net/api, apiKey: process.env.TAOTOKEN_API_KEY || 你的_API_KEY, model: qwen3 }, // 本地服务端口 server: { mainPort: 3000, proxyPort: 3001 }, // 12306-MCP 服务地址 mcp: { baseUrl: http://localhost:8080/mcp } };把 API Key 放在环境变量里更安全启动前执行export TAOTOKEN_API_KEY你的KeyWindows 用set TAOTOKEN_API_KEY你的Key。3.4 proxy-server.js解决 CORS 并转发请求// proxy-server.js const http require(http); const config require(./config); const PROXY_PORT config.server.proxyPort; const MCP_BASE config.mcp.baseUrl; async function readRequestBody(req) { let data ; for await (const chunk of req) data chunk; return data; } const server http.createServer(async (req, res) { res.setHeader(Access-Control-Allow-Origin, *); res.setHeader(Access-Control-Allow-Methods, GET, POST, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type); if (req.method OPTIONS) { res.writeHead(204); res.end(); return; } try { const body await readRequestBody(req); const targetUrl MCP_BASE req.url; const upstream await fetch(targetUrl, { method: req.method, headers: { Content-Type: application/json }, body: req.method POST ? body : undefined }); const result await upstream.text(); res.writeHead(upstream.status, { Content-Type: application/json }); res.end(result); } catch (err) { res.writeHead(502, { Content-Type: application/json }); res.end(JSON.stringify({ error: err.message })); } }); server.listen(PROXY_PORT, () { console.log(代理服务器已启动: http://localhost:${PROXY_PORT}); });3.5 server.js主服务器// server.js const http require(http); const fs require(fs); const path require(path); const config require(./config); const PORT config.server.mainPort; const server http.createServer((req, res) { let filePath req.url / ? /index.html : req.url; filePath path.join(__dirname, filePath); fs.readFile(filePath, (err, data) { if (err) { res.writeHead(404, { Content-Type: text/html }); res.end(h1404 Not Found/h1); return; } const ext path.extname(filePath); const type ext .html ? text/html : application/javascript; res.writeHead(200, { Content-Type: type }); res.end(data); }); }); server.listen(PORT, () { console.log(主服务器已启动: http://localhost:${PORT}); });3.6 start-ticket-assistant.js一键启动// start-ticket-assistant.js const { spawn } require(child_process); function run(script) { const child spawn(node, [script], { stdio: inherit }); child.on(error, (err) console.error(${script} 启动失败:, err)); return child; } console.log(正在启动 12306 火车票智能查询助手...); run(server.js); run(proxy-server.js);3.7 通义灵码侧的 settings 配置如果你在通义灵码里配置模型服务对应的 settings 片段如下字段名按客户端实际要求调整{ model.provider: openai-compatible, model.baseUrl: https://taotoken.net/api, model.apiKey: 你的_API_KEY, model.modelId: qwen3, mcp.servers: [12306-mcp] }注意Base URL、API Key、Model ID 这三件套必须同时正确。少一个都会在调用时报错后面第 5 节会逐个对照真实报错。配置到这里就完整了。接下来验证它到底能不能跑通。4. 验证请求从启动服务到拿到武汉到广州的真实车次结果配置写完不代表能用必须一步步验证。这一节我按「启动 → 单接口测试 → 自然语言查询」的顺序走每一步都给出预期结果。4.1 启动服务在项目目录下执行node start-ticket-assistant.js预期输出正在启动 12306 火车票智能查询助手... 主服务器已启动: http://localhost:3000 代理服务器已启动: http://localhost:3001如果只看到一个服务起来说明另一个端口被占用了去第 5 节看端口冲突的排查。4.2 验证环境检测脚本node check-environment.js这个脚本会打印 Node.js 版本和当前配置。确认baseUrl是https://taotoken.net/apimodel是qwen3。4.3 单接口验证查车站代码先不碰大模型直接测代理转发是否通。用 curlcurl -X POST http://localhost:3001/api/station-code-of-citys \ -H Content-Type: application/json \ -d {citys: [武汉, 广州]}预期返回类似{ 武汉: WHN, 广州: GZQ }如果这一步就失败说明代理到 12306-MCP 的链路有问题先别往下走。4.4 单接口验证查余票curl -X POST http://localhost:3001/api/tickets \ -H Content-Type: application/json \ -d { from: WHN, to: GZQ, date: 2025-10-01 }预期返回一个车次数组每个元素包含车次号、出发时间、到达时间、各席别余票。日期换成你实际要查的。4.5 自然语言查询qwen3 调用 MCP打开浏览器访问http://localhost:3000在输入框里输入请帮我查询一下今天下午17:30之前武汉到广州的火车票预期行为页面把这句话发给本地服务本地服务调用 TaoToken 的 qwen3qwen3 解析出「出发地武汉、目的地广州、时间上限17:30」然后决定调用 12306-MCP 的station-code-of-citys和tickets两个工具拿到结果后组织成自然语言返回。预期结果类似17:30 之前武汉到广州有以下车次 G1001 08:00-12:30 二等座有票 G1003 09:15-13:50 二等座有票 G1005 10:40-15:10 二等座候补 ...4.6 验证成功的判断标准三个条件同时满足才算跑通第一浏览器控制台没有 CORS 报错。第二Network 面板里能看到对localhost:3001的请求返回 200。第三页面显示的车次信息和你手动在 12306 查到的对得上。如果车次对不上先检查日期格式和车站代码是否正确。车站代码用错是最常见的「查得到但结果不对」的原因。4.7 把查询结果做成提醒验证通过后你可以在12306-mcp-client.js里加一个定时器每隔几分钟查一次发现目标车次余票从「无」变「有」就打印提醒setInterval(async () { const result await queryTickets(WHN, GZQ, 2025-10-01); const target result.find(t t.trainNo G1001); if (target target.seats[二等座] ! 无) { console.log(有票了:, target.trainNo, target.seats[二等座]); } }, 5 * 60 * 1000);这就是「余票提醒」的最小实现。不需要复杂框架一个定时器加一次查询就够。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 逐个对照这一节把我在调试过程中真实遇到的报错列出来每个都给出原因和修法。你遇到问题时直接对照。5.1 401 Unauthorized现象调用 qwen3 时返回 401或者浏览器 Network 里看到401。原因API Key 没填、填错、或者环境变量没生效。排查步骤先确认环境变量echo $TAOTOKEN_API_KEY如果输出为空说明没设置成功。重新执行export或者直接在config.js里临时写死测试。再确认 Key 没有多余空格。从网页复制时经常带上换行或空格用trim()处理apiKey: (process.env.TAOTOKEN_API_KEY || ).trim()最后确认 Base URL 是https://taotoken.net/api不要多写/v1或少写/api。5.2 local proxy failed现象前端报local proxy failed或Failed to fetch。原因代理服务器没启动或者端口不对。排查步骤先看代理是否在跑netstat -ano | findstr :3001Windows 用上面这条macOS/Linux 用lsof -i :3001如果没有输出说明proxy-server.js没起来。单独启动它看报错node proxy-server.js如果报EADDRINUSE就是端口被占用去 5.3。5.3 端口冲突 EADDRINUSE现象Error: listen EADDRINUSE: address already in use :::3000。原因3000 或 3001 被别的程序占了。排查步骤netstat -ano | findstr :3000找到最后一列的 PID然后taskkill /PID pid /FmacOS/Linuxkill -9 pid或者干脆改config.js里的端口号换成 6000/6001 之类不常用的。5.4 reading choices of undefined现象TypeError: Cannot read properties of undefined (reading choices)。原因模型返回结构和你代码里取值的路径不一致。通常是请求根本没成功返回的是错误对象但代码直接去取response.choices。修法在取值前先判断const data await res.json(); if (!data.choices || !data.choices[0]) { console.error(模型返回异常:, JSON.stringify(data)); return; } const content data.choices[0].message.content;同时检查 Model ID 是否写对。Model ID 错误时有些网关会返回错误结构而不是标准 choices。5.5 OAuth 相关报错现象出现OAuth、token expired、invalid_grant之类字样。原因如果你用的是需要 OAuth 流程的客户端比如某些 IDE 插件token 过期或授权范围不对。修法重新走一遍授权流程或者在客户端里重新登录。如果你用的是 API Key 方式本篇的方式一般不会碰到 OAuth出现这个报错说明客户端配置成了 OAuth 模式改回 API Key 模式即可。5.6 三件套检查清单出现任何调用失败先按这个清单过一遍检查项正确值常见错误Base URLhttps://taotoken.net/api多写/v1、少写/apiAPI Key创建的那串无空格复制带换行、用了旧 KeyModel ID平台确认的 qwen3 标识写成qwen-3、qwen3.5等不存在名称这三项任意一项错都会导致调用失败。先把它们对齐再去看别的。5.7 CORS 仍然报错如果代理已经设置了Access-Control-Allow-Origin但浏览器还是拦检查是不是请求直接打到了 12306-MCP 而不是代理。前端所有请求都应该指向localhost:3001不要指向 MCP 的原始地址。6. 把查询助手用起来接入文档、模型对话与长期编码的选择走到这里你应该已经能在浏览器里输入自然语言拿到武汉到广州的车次和余票了。剩下的就是把它用起来以及后续怎么扩展。如果你在接入过程中卡住优先看接入文档里面有针对不同客户端的配置示例https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你想先验证 qwen3 对自然语言的理解能力不写代码直接在模型对话页面测试「今天下午 17:30 之前武汉到广州的火车票」这类问法看它能不能正确解析出参数https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你打算把这个助手做成长期运行的服务比如节假日期间一直挂着盯票或者继续加功能多城市监控、微信推送、Docker 部署Coding Plan 更适合这种持续迭代的场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite如果你需要管理多个 Key 或查看调用量控制台在这里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite最后说一个实际经验节假日查询频率不要设太高几分钟一次足够。查得太频繁一方面没必要另一方面 12306 的接口也有自己的节奏。把定时器设成 5 分钟配合「只在余票状态变化时提醒」的逻辑既省资源又不会漏掉放票。代码跑通之后你会发现真正有价值的不是「查一次票」而是「持续盯着有变化就告诉你」。这才是这个助手相比手动刷 App 的核心优势。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑