NodeJS version of lad.lviv.ua API
- ✓License: WTFPL
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Mature repo (>1y old)
- ✓Documented (README)
git clone https://github.com/vbhjckfd/timetable-api-node{
"mcpServers": {
"timetable-api-node": {
"command": "node",
"args": ["/path/to/timetable-api-node/dist/index.js"],
"env": {
"NEW_RELIC_LICENSE_KEY": "<new_relic_license_key>"
}
}
}
}NEW_RELIC_LICENSE_KEYResumen de MCP Servers
# Timetable API Node
[](https://github.com/vbhjckfd/timetable-api-node/actions/workflows/ci.yml)
[](https://github.com/vbhjckfd/timetable-api-node/blob/master/.nvmrc)
[](https://github.com/vbhjckfd/timetable-api-node/blob/master/LICENSE)
[](https://registry.modelcontextprotocol.io/v0/servers/io.github.vbhjckfd%2Ftimetable-api-node/versions)
Express-based API for Lviv transport timetable data with a read-only MCP endpoint.
[](https://smithery.ai/servers/vbhjckfd/lad-lviv-ua)
[](https://glama.ai/mcp/servers/vbhjckfd/timetable-api-node)
[](https://glama.ai/mcp/servers/vbhjckfd/timetable-api-node)
## Requirements
- Node.js 26 (see `.nvmrc`)
## Run locally
```bash
nvm use
make start
```
## Test
```bash
nvm use && make test
```
## Monitoring
Two optional integrations, both off unless their environment variable is set:
| Variable | Effect |
| --- | --- |
| `SENTRY_DSN` | Error reporting via `instrument.js` |
| `NEW_RELIC_LICENSE_KEY` | New Relic APM via `newrelic.cjs` |
New Relic runs as a preloaded agent, so `npm start` carries the flags:
```bash
node -r dotenv/config -r newrelic --import newrelic/esm-loader.mjs index.js
```
`dotenv/config` is preloaded first so `.env` is populated before the agent
reads its configuration. The config file is `newrelic.cjs` (the agent is
CommonJS and this project is ESM) and holds no secrets — the key comes from the
environment. `/health` is excluded from transactions via `rules.ignore`.
The account is in the **EU** region; its license key starts with `eu01xx` and
the agent picks the collector from that prefix. Use the 40-character ingest
license key, not an `NRAK-...` user API key.
Cloud Run reads the key from Secret Manager:
```bash
gcloud run services update timetable-api-node --region=us-central1 --project=timetable-252615 --set-secrets=NEW_RELIC_LICENSE_KEY=new-relic-license-key:latest
```
## MCP Server
This service exposes a public read-only MCP endpoint over Streamable HTTP.
- MCP endpoint: `/mcp`
- Server card: `/.well-known/mcp/server-card.json`
- Discovery hint: `/robots.txt` (non-standard comment hint)
Production deployment (see `cloudbuild.yaml` for Cloud Run) serves **REST and MCP** from **[api.lad.lviv.ua](https://api.lad.lviv.ua)**. The main site **[lad.lviv.ua](https://lad.lviv.ua)** is the public transport website (this repo still links there in HTML sitemap and tables for people, not for the API host). Use your own origin when running locally.
### LLM and `/mcp` flow
An MCP client (Claude, Cursor, or the MCP SDK) talks JSON-RPC over **Streamable HTTP** to `POST /mcp`. Tool handlers reuse the same Express actions as the REST API, backed by **LokiJS** timetable data, **GTFS** SQLite (via `gtfs`), and **live GTFS-RT** feeds (for example `track.ua-gis.com`).
```mermaid
graph LR;
Client[LLM or MCP client] -->|JSON-RPC Streamable HTTP| Mcp["POST /mcp"];
Mcp --> Tools[Tool handlers];
Tools --> Actions[Express actions];
Actions --> Loki[(LokiJS)];
Actions --> Gtfs[(GTFS SQLite)];
Actions --> Rt[GTFS-RT upstream];
Loki --> Actions;
Gtfs --> Actions;
Rt --> Actions;
Actions --> Tools;
Tools --> Mcp;
Mcp -->|MCP tool result| Client;
```
### Try the live API
[](https://api.lad.lviv.ua/.well-known/mcp/server-card.json)
[](https://api.lad.lviv.ua/stops.json)
[](https://api.lad.lviv.ua/routes.json)
**MCP Inspector (local):** run `npx @modelcontextprotocol/inspector`, then open the UI with transport and server URL prefilled (from the [inspector README](https://github.com/modelcontextprotocol/inspector/blob/main/README.md)):
`http://localhost:6274/?transport=streamable-http&serverUrl=https%3A%2F%2Fapi.lad.lviv.ua%2Fmcp`
<details>
<summary><strong>Postman / curl: call a tool on production</strong></summary>
`POST https://api.lad.lviv.ua/mcp` with `Content-Type: application/json`. The Streamable HTTP transport may require additional headers your MCP client sets automatically; for a quick manual test, follow the same sequence your MCP SDK uses (session `initialize`, then `tools/call`). Example **`tools/call`** body shape:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_stop_realtime",
"arguments": { "stop_id": 101 }
}
}
```
Successful tool responses return a **natural-language text summary** inside MCP `content` items (`type: "text"`) — e.g. *"Stop «Opera»: 3 arrivals. Next: T01 → «Rynok» in 2 min."* The full structured payload is in the `structuredContent` field (for schema-aware clients). Each `structuredContent` payload follows a strict UI contract:
```json
{
"view": "transit_realtime",
"data": { "...": "tool-specific source data" },
"ui_blocks": [
{ "type": "map", "data": { "...": "map renderer input" } },
{ "type": "arrival_list", "data": { "...": "arrival list renderer input" } }
]
}
```
Consistency rule: each vehicle rendered on map must either have a matching ETA in list data or `eta_status: "unassigned"`.
</details>
### Exposed tools
- `get_stop_realtime`
- `get_route_static`
- `get_route_realtime`
- `get_stop_geometry`
- `get_stops_around_location`
- `get_nearby_vehicles`
- `get_vehicle_info`
<details>
<summary><code>get_stop_realtime</code> — input & example</summary>
**Arguments (JSON):**
| Field | Type | Required |
|-------|------|----------|
| `stop_id` | positive integer or digits-only string | yes |
**Example result** (shape only; values from upstream):
```json
{
"view": "transit_realtime",
"data": {
"stop": { "id": "707", "name": "Стадіон Сільмаш", "lat": 49.84, "lng": 24.03 },
"arrivals": [
{
"route": "T30",
"direction": "Рясівська",
"vehicle_type": "tram",
"arrival_minutes": 4,
"vehicle_id": "tram_123",
"lat": 49.83,
"lng": 24.02,
"bearing": 120
}
],
"updated_at": "2026-01-23T12:00:00Z"
},
"ui_blocks": [
{
"type": "map",
"data": { "center": [49.84, 24.03], "vehicles": [] }
},
{
"type": "arrival_list",
"data": { "arrivals": [] }
}
]
}
```
</details>
<details>
<summary><code>get_route_static</code> — input & example</summary>
**Arguments (JSON):**
| Field | Type | Required |
|-------|------|----------|
| `route_name` | route short name (e.g. `"T30"`, `"32A"`) or numeric external ID | yes |
**Example result** (shape only; stops truncated for brevity):
```json
{
"view": "transit_realtime",
"data": {
"route": {
"name": "T30",
"long_name": "Рясне-2 — Сихів",
"color": "#e81717",
"type": "tram"
},
"stops": [
[
{
"id": "101", "name": "Головний вокзал", "lat": 49.841, "lng": 24.003,
"departures": ["05:30", "05:52"],
"schedule": { "workday": ["05:30", "05:52", "06:10"], "weekend": ["07:00", "07:30"] }
},
{ "id": "707", "name": "Стадіон Сільмаш", "lat": 49.838, "lng": 24.021, "departures": [], "schedule": { "workday": [], "weekend": [] } }
],
[
{ "id": "707", "name": "Стадіон Сільмаш", "lat": 49.838, "lng": 24.021, "departures": [], "schedule": { "workday": [], "weekend": [] } },
{ "id": "101", "name": "Головний вокзал", "lat": 49.841, "lng": 24.003, "departures": [], "schedule": { "workday": [], "weekend": [] } }
]
],
"shapes": [
[[49.841, 24.003], [49.839, 24.012], [49.838, 24.021]],
[[49.838, 24.021], [49.839, 24.012], [49.841, 24.003]]
],
"updated_at": "2026-01-23T12:00:00Z"
},
"ui_blocks": [
{
"type": "map",
"data": {
"center": [49.841, 24.003],
"zoom": 13,
"polylines": [[[49.841, 24.003], [49.839, 24.012], [49.838, 24.021]]],
"stops": [
{ "id": "101", "name": "Головний вокзал", "lat": 49.841, "lng": 24.003 },
{ "id": "707", "name": "Стадіон Сільмаш", "lat": 49.838, "lng": 24.021 }
],
"vehicles": []
}
}
]
}
```
`stops[0]` is direction 0 (outbound), `stops[1]` is direction 1 (return). `departures` and `schedule` are populated only for the **first stop of direction 0**; all other stops have empty arrays. `schedule.workday` contains Monday–Friday departure times; `schedule.weekend` contains Saturday–Sunday departure times. `departures` keeps today's schedule for backward compatibility. `shapes` follows the same two-element order. The map block uses direction-0 polyline and all unique stops as markers.
</details>
<details>
<summary><code>get_route_realtime</code> — input & example</summary>
**Arguments (JSON):**
| Field | Type | Required |
|-------|------|----------|
| `route_name` | route short name (e.g. `"T30"`, `"32A"`) or numeric external ID | yes |
**Example result:**
```json
{
"view": "transit_realtime",
"data": {
"route_name": "T30",
"vehicles": [
{
"id": "tram_123",
"direction": 0,
"lat": 49.838,
"lng": 24.021,
"bearing": 120,
"lowfloor": true
}
],
"updated_at": "2026-01-23T12:00:00Z"
},
"uLo que la gente pregunta sobre timetable-api-node
¿Qué es vbhjckfd/timetable-api-node?
+
vbhjckfd/timetable-api-node es mcp servers para el ecosistema de Claude AI. NodeJS version of lad.lviv.ua API Tiene 3 estrellas en GitHub y su última actualización registrada es del 2026-09-08.
¿Cómo se instala timetable-api-node?
+
Puedes instalar timetable-api-node clonando el repositorio (https://github.com/vbhjckfd/timetable-api-node) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar vbhjckfd/timetable-api-node?
+
Nuestro agente de seguridad ha analizado vbhjckfd/timetable-api-node y le ha asignado un Trust Score de 90/100 (tier: Verified). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene vbhjckfd/timetable-api-node?
+
vbhjckfd/timetable-api-node es mantenido por vbhjckfd. La última actividad registrada en GitHub es del 2026-09-08, con 0 issues abiertos.
¿Hay alternativas a timetable-api-node?
+
Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.
Despliega timetable-api-node en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/vbhjckfd-timetable-api-node)<a href="https://claudewave.com/repo/vbhjckfd-timetable-api-node"><img src="https://claudewave.com/api/badge/vbhjckfd-timetable-api-node" alt="Featured on ClaudeWave: vbhjckfd/timetable-api-node" width="320" height="64" /></a>Más MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
The fastest path to AI-powered full stack observability, even for lean teams.
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!