公交数据服务

TUS Santander MCP Server是一款用于获取和处理Santander公交及TUeBICI数据的服务,提供结构化输出和交互式地图工具。

  • by NullTrisp
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 NullTrisp/tus-mcp

使用说明

TUS Santander MCP Server

MCP server for Santander's TUS bus and TUeBICI data. Tools read the official Santander Open Data API or the official TUeBICI operator GBFS feed over HTTPS, validate responses with Zod, and return structured output.

Tools

  • santander_get_bus_stops
    • limit: integer from 1 to 100; defaults to 10.
    • search: optional name, address, public stop number, or API resource ID.
    • Exact public stop-number matches are returned first.
  • santander_get_nearby_bus_stops
    • Uses ChatGPT's optional approximate openai/userLocation metadata by default.
    • latitude and longitude: optional fallback coordinates; provide both or neither.
    • radiusMeters: integer from 50 to 5000; defaults to 1000.
    • limit: integer from 1 to 20; defaults to 5.
    • Returns stops ordered by distance_meters.
  • santander_get_bus_lines
    • search: optional public line number, name, or API resource ID.
  • santander_get_bus_line_stops
    • lineId: required public line number from lineas_bus["ayto:numero"], such as 24C1 or N2.
    • Returns raw stop relationships plus ordered route variants from lineas_bus_secuencia.
    • Ordered stops include sequence, published distance, name, address when available, and WGS84 coordinates.
  • santander_get_bus_estimations
    • stopId: optional public stop number from paradas_bus["ayto:numero"], such as 15.
    • lineId: optional public line number, such as 1 or 24C1.
    • limit: integer from 1 to 100; defaults to 20.
  • santander_get_recent_bus_positions
    • lineId: optional public line number.
    • maxAgeMinutes: observation window from 1 to 120 minutes; defaults to 15.
    • Returns the newest observation per vehicle with public line, speed, capacity, length, and fuel type when available.
  • santander_get_tus_recharge_points
    • search: optional name, address, postcode, town, or vendor type.
    • limit: integer from 1 to 100; defaults to 20.
  • santander_get_nearby_tus_recharge_points
    • Uses ChatGPT's optional approximate location metadata and returns recharge points ordered by distance.
    • Accepts the same coordinate fallback, radiusMeters, and limit options as the nearby bus-stop tool.
  • santander_get_tuebici_stations
    • search: optional station name or public short number.
    • onlyAvailable: only return stations currently renting with at least one bicycle; defaults to false.
    • limit: integer from 1 to 100; defaults to 20.
    • Joins station information and availability from the official TUeBICI/Nextbike GBFS feed.
  • santander_get_nearby_tuebici_stations
    • Uses ChatGPT's optional approximate location metadata and returns stations ordered by distance.
    • Accepts the coordinate fallback, radiusMeters, limit, and onlyAvailable.
  • santander_render_bus_stops_map
    • Renders up to 100 selected stops as clickable pins in an interactive OpenStreetMap widget.
    • Call a stop data tool first, then pass each stop's public ID, name, and numeric WGS84 latitude/longitude.
    • Supports inline and fullscreen display in ChatGPT while leaving the data tools usable in clients without UI support.
  • santander_render_bus_lines_map
    • Renders up to 10 lines as distinct colored, clickable markers.
    • Pass ordered routes from santander_get_bus_line_stops for dashed schematic paths, or plain stops for marker-only display.
    • Dashed paths show published stop order, not road geometry.
  • santander_render_bus_positions_map
    • Renders up to 100 recent bus positions, colored by public line.
    • Call santander_get_recent_bus_positions first and pass its positions array.
  • santander_render_tus_recharge_points_map
    • Renders up to 100 TUS sale and recharge points as clickable markers.
    • Call santander_get_tus_recharge_points first and pass its points array.
  • santander_render_tuebici_stations_map
    • Renders up to 100 TUeBICI stations, with availability shown by marker color.
    • Call santander_get_tuebici_stations first and pass its stations array.

The estimates tool preserves the API's exact signed integer values as arrival_seconds and distance_meters. Empty second-bus values become null; they are not presented as inferred arrival states. Recent positions are filtered at the Open Data API before download and deduplicated by vehicle. Each result includes source_urls and fetched_at, while time-sensitive records include their observation timestamp.

The official datasets occasionally contain live estimate references that are absent from the stop or line master dataset. Those estimates remain queryable because they are still validated against the live dataset, and the result includes a warnings entry instead of silently discarding official data.

The santander_bus_stop_info prompt also expects a public stop number. It validates that number against the current API before creating the prompt.

Identifier semantics

Santander's datasets use two different identifiers:

  • dc:identifier is the internal API resource ID.
  • ayto:numero is the public stop or line number.

Tool inputs use public numbers. Internally, line-stop relationships reference a line's dc:identifier; the server resolves this mapping from the current lineas_bus dataset before filtering. Values such as ayto:15 are not valid identifiers.

Validation and failure behavior

  • MCP inputs and structured outputs use Zod schemas.
  • API envelopes and every known resource field are validated at runtime.
  • All selected pages are fetched, checked for consistent summaries, and deduplicated by dc:identifier or stable resource URI.
  • Invalid JSON, malformed API data, partial pagination, unknown public IDs, timeouts, and non-success HTTP responses fail closed.
  • Unknown upstream fields are removed from output until they have an explicit schema.

Requirements

  • Node.js 22 or newer
  • npm

Setup

npm install
npm run build
npm test

Create a .env file:

PORT=3000
ALLOWED_HOSTS=localhost,127.0.0.1
WIDGET_DOMAIN=https://localhost
OPENAI_APPS_CHALLENGE=replace-with-the-value-from-chatgpt
  • PORT must be an integer from 1 to 65535.
  • ALLOWED_HOSTS is a comma-separated host list. Entries are normalized to lowercase and must not include a protocol, port, or path.
  • WIDGET_DOMAIN is the unique HTTPS origin used to isolate the map widget. It defaults to the first ALLOWED_HOSTS entry prefixed with https://.
  • OPENAI_APPS_CHALLENGE is the domain-verification value supplied during ChatGPT app submission. When set, the server returns it as plain text from /.well-known/openai-apps-challenge.

The endpoint is public because it only exposes public, read-only Santander data. It does not persist user location. When ChatGPT provides optional approximate location metadata, the nearby tools process it transiently to calculate distances; the server does not log it or use it for authorization or security decisions.

Run in development or production:

npm run dev
npm run build
npm start

The stateless Streamable HTTP endpoint is POST http://localhost:3000/mcp. GET and DELETE return method-not-allowed responses.

Inspector

npm run inspect

Connect to http://localhost:3000/mcp.

Google Cloud Run

Deploy publicly and set ALLOWED_HOSTS to the hostname Cloud Run assigns to the service (without https://):

gcloud run deploy tus-mcp --source . --region REGION --allow-unauthenticated --set-env-vars ALLOWED_HOSTS=YOUR_SERVICE_HOSTNAME,WIDGET_DOMAIN=https://YOUR_SERVICE_HOSTNAME,OPENAI_APPS_CHALLENGE=replace-with-the-value-from-chatgpt --project=PROJECT

The MCP endpoint will be https://YOUR_SERVICE_HOSTNAME/mcp.

Docker

docker build -t tus-mcp .
docker run -p 3000:3000 --env-file .env tus-mcp

Project structure

  • src/index.ts: validated startup configuration and HTTP listener.
  • src/app.ts: Express/MCP transport setup.
  • src/config.ts: Zod-validated environment configuration.
  • src/mcp/: tools, prompt, and MCP server registration.
  • src/services/: paginated Santander Open Data access and ID resolution.
  • src/types/: Zod API/input/output contracts and inferred TypeScript types.