mcp-server-12306: a Model Context Protocol backend for China Railway ticket queries
12306 MCP Server 是一个基于 Model Context Protocol (MCP) 的高性能火车票查询后端系统。它通过标准化接口提供官方 12306 的实时数据服务,包括余票查询、车站信息、列车经停站、中转换乘方案等核心功能。
At a glance
- What is it?
- The project wraps 12306's public query endpoints in seven MCP tools and ships two transports over one core. It is useful if you already run an MCP client and want live train data inside it; it is not a booking system, and the README does not describe rate limits or caching.
- Who is it for?
- Adopt it if you already have an MCP client and need read-only 12306 lookups inside it, and you accept that every call reaches the live 12306 interface with no documented caching layer. Do not adopt it if you need ticket booking, order handling or an offline dataset; the tool list contains no write operations.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap it fills between an agent and 12306's own query interface
An agent that can reason about travel still cannot see seat availability unless something hands it a structured answer. The README frames the project as a backend that exposes 12306's real-time data through the Model Context Protocol, covering remaining tickets, station information, train stopping points and transfer itineraries. The audience is narrow and specific: people running MCP-capable clients such as Claude Desktop or Cursor, or writing automation that speaks JSON-RPC. The seven tools are all read operations. There is no booking, no order submission and no passenger identity handling anywhere in the tool table, which is the correct scope for a query service but also the boundary you should notice before proposing it to a team that expects to complete a purchase. The station search covers a stated 3382 or more stations with fuzzy matching across Chinese characters, pinyin, initials and three-letter codes, which matters because 12306 station identifiers are not the names humans type.
One core module, two transports, and why that matters for behaviour
The layout puts tool registration and dispatch in src/mcp_12306/server.py, described in the README as transport-agnostic, with TOOL_HANDLERS holding registration and call_tool handling dispatch. stdio_server.py and http_server.py are thin layers over that same instance, so a tool behaves identically whether it is reached over standard input or over the network. Tool schemas have a single source in ticket_service.MCP_TOOLS, and the HTTP /schema/tools endpoint is generated from it. That is a real design decision rather than a packaging detail: it removes the common failure where a remote deployment drifts from the local one. Business logic sits in services/, split between station_service.py (loading, searching and code conversion for station data) and ticket_service.py (the seven tool implementations). Static station data lives in resources/ as station_name.js, and scripts/update_stations.py exists to refresh it. Date validation is isolated in utils/date_utils.py, which is where you would look if a query fails on a malformed train_date rather than on a network error.
Installing and running it: three paths from the README
The Python requirement is >= 3.10 and < 3.14, with uv recommended but pip and pipx supported. For a local client, the README's configuration block uses uvx as the command with mcp-server-12306 as the single argument, and notes that stdio mode occupies no network port. Alternatives shown are pipx run --no-cache mcp-server-12306 and, for source checkouts, uv sync followed by a uv invocation with --directory pointing at the clone. For remote deployment, mcp-12306 starts the server directly, or uv run python scripts/start_server.py from source, listening on port 8000 by default with the MCP endpoint at /mcp. The HTTP surface also exposes /health, which reports the loaded station count and active session count, /schema/tools for the full JSON Schema of every tool, and / for version and protocol information. Docker is the third route: docker run -d -p 8000:8000 --name mcp-server-12306 drfccv/mcp-server-12306:latest, with SERVER_HOST and SERVER_PORT passed as environment variables when remapping ports. Four configuration keys are documented: SERVER_HOST defaulting to 0.0.0.0, SERVER_PORT defaulting to 8000, DEBUG defaulting to false, and LOG_LEVEL defaulting to INFO. They can be set in the environment or in a .env file at the project root. Note that SERVER_HOST defaults to all interfaces, so a container started without overriding it is reachable from outside the host unless your network rules say otherwise.
What the tool set does and does not cover
query-tickets takes from_station, to_station and train_date and returns availability, train numbers, seat classes and times in one call, with filtering by train number. query-ticket-price takes the same three required parameters and returns fares per seat class. search-stations takes a single query string. query-transfer takes the same three parameters as the ticket tools and, per the README, paginates through official transfer options to return complete paths with waiting times. get-train-route-stations needs train_no, from_station, to_station and train_date, which is a heavier parameter set than the name suggests: you cannot ask for a train's stops from the train number alone. get-train-no-by-train-code exists because a train code shown to a passenger is not the same as the official internal number, so this tool bridges the two before you call the route tool. get-current-time takes no parameters and returns the current time in any timezone plus relative date calculation, which the README positions as help for choosing a travel date. That last tool is the tell that the authors expect an agent to construct dates itself, and it is a sensible guard against a model inventing tomorrow's date.
Limitations the README leaves open
There is no caching layer described anywhere. Every call to query-tickets or query-ticket-price reaches the 12306 interface, and the README's feature table says the data is real-time. For an agent that retries or fans out queries across dates, that means request volume scales directly with model behaviour, and nothing in the documented configuration lets you cap it. Rate limiting, backoff and retry policy are not mentioned. The upstream interface is also not yours: 12306 can change response shapes or add anti-automation measures, and the project has no released versions retrieved, so there is no changelog to consult when something breaks. The station dataset is a bundled file, station_name.js, refreshed by scripts/update_stations.py, so station data ages until someone runs that script. On the protocol side, the README claims automatic negotiation based on MCP SDK v2, compatible with a handshake-era protocol dated 2025-11-25 and a modern one dated 2026-07-28. Those are future-dated version strings in the material, and I cannot verify from the README what the fallback behaviour is when a client speaks neither. Treat protocol compatibility as something to confirm against your client rather than as a settled property. Finally, the service is a query front end for a ticketing system you do not control; if 12306 is slow or refuses a request, the MCP tool surfaces that, and the README does not describe error shapes.
Compared with writing your own 12306 client
The realistic alternative is not another MCP server; it is a small script or library that calls 12306's query endpoints directly and formats the result for your own application. That approach gives you control over caching, retry policy and response shaping, and it does not require an MCP client in the loop. What it costs you is the parts this project already solved: station name resolution across four input forms, the train code to official number mapping, transfer pagination, and the schema plumbing that lets a model call the functions correctly. If your consumer is a model or an MCP-aware assistant, the tool schemas are the expensive part to get right, and reusing MCP_TOOLS as the single schema source is cheaper than re-deriving it. If your consumer is a conventional web service with its own database and cache, the MCP layer is overhead, and a direct client against 12306 is the shorter path. The decision turns on whether an MCP client is already in your stack, not on which code is better written.
Maintenance, licensing and what a fork inherits
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive grant and I am not offering legal advice; if you redistribute the server inside a product, read the LICENSE file in the repository and follow your own counsel on attribution. Maintenance cost concentrates in two places. First, the station dataset, which is a static file refreshed by scripts/update_stations.py; if new stations open and nobody runs it, fuzzy search silently misses them. Second, the upstream 12306 interface, which the project does not control and cannot pin. The codebase itself is small and split cleanly, with mypy in strict mode and black plus isort in the documented development loop, so contributing a fix is not a large undertaking. Dependencies are locked via uv.lock, and the Docker image is a multi-stage build on python:3.12-alpine, so the container's Python is fixed at 3.12 even though the package supports 3.10 through 3.13. If you vendor this, budget for a periodic station refresh and for reading the upstream interface when a query tool starts returning empty results.
Editorial conclusion
Adopt it if you already have an MCP client and need read-only 12306 lookups inside it, and you accept that every call reaches the live 12306 interface with no documented caching layer. Do not adopt it if you need ticket booking, order handling or an offline dataset; the tool list contains no write operations. Before wiring it into anything, run the server in HTTP mode and check the /health endpoint for the loaded station count, then confirm that query-tickets returns the seat categories you care about for a route you already know.
Community notes