使用说明
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_stopslimit: 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/userLocationmetadata by default. latitudeandlongitude: 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.
- Uses ChatGPT's optional approximate
santander_get_bus_linessearch: optional public line number, name, or API resource ID.
santander_get_bus_line_stopslineId: required public line number fromlineas_bus["ayto:numero"], such as24C1orN2.- 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_estimationsstopId: optional public stop number fromparadas_bus["ayto:numero"], such as15.lineId: optional public line number, such as1or24C1.limit: integer from 1 to 100; defaults to 20.
santander_get_recent_bus_positionslineId: 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_pointssearch: 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, andlimitoptions as the nearby bus-stop tool.
santander_get_tuebici_stationssearch: optional station name or public short number.onlyAvailable: only return stations currently renting with at least one bicycle; defaults tofalse.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, andonlyAvailable.
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_stopsfor 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_positionsfirst and pass itspositionsarray.
santander_render_tus_recharge_points_map- Renders up to 100 TUS sale and recharge points as clickable markers.
- Call
santander_get_tus_recharge_pointsfirst and pass itspointsarray.
santander_render_tuebici_stations_map- Renders up to 100 TUeBICI stations, with availability shown by marker color.
- Call
santander_get_tuebici_stationsfirst and pass itsstationsarray.
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:identifieris the internal API resource ID.ayto:numerois 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:identifieror 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
PORTmust be an integer from 1 to 65535.ALLOWED_HOSTSis a comma-separated host list. Entries are normalized to lowercase and must not include a protocol, port, or path.WIDGET_DOMAINis the unique HTTPS origin used to isolate the map widget. It defaults to the firstALLOWED_HOSTSentry prefixed withhttps://.OPENAI_APPS_CHALLENGEis 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.