Skip to main content
ClaudeWave
vbhjckfd avatar
vbhjckfd

timetable-api-node

View on GitHub

NodeJS version of lad.lviv.ua API

MCP ServersOfficial Registry3 stars3 forksJavaScriptWTFPLUpdated today
ClaudeWave Trust Score
90/100
Verified
Passed
  • License: WTFPL
  • Actively maintained (<30d)
  • Clear description
  • Topics declared
  • Mature repo (>1y old)
  • Documented (README)
Last scanned: 9/9/2026
Install in Claude Code / Claude Desktop
Method: Manual
Claude Code CLI
git clone https://github.com/vbhjckfd/timetable-api-node
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "timetable-api-node": {
      "command": "node",
      "args": ["/path/to/timetable-api-node/dist/index.js"],
      "env": {
        "NEW_RELIC_LICENSE_KEY": "<new_relic_license_key>"
      }
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Clone https://github.com/vbhjckfd/timetable-api-node and follow its README for install instructions.
Detected environment variables
NEW_RELIC_LICENSE_KEY
Use cases

MCP Servers overview

# Timetable API Node

[![CI](https://img.shields.io/github/actions/workflow/status/vbhjckfd/timetable-api-node/ci.yml?branch=master&logo=github&label=CI)](https://github.com/vbhjckfd/timetable-api-node/actions/workflows/ci.yml)
[![Node.js](https://img.shields.io/badge/node.js-26-43853d?logo=node.js&logoColor=white)](https://github.com/vbhjckfd/timetable-api-node/blob/master/.nvmrc)
[![License: WTFPL](https://img.shields.io/github/license/vbhjckfd/timetable-api-node?label=license)](https://github.com/vbhjckfd/timetable-api-node/blob/master/LICENSE)
[![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6366f1?style=flat-square)](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.

[![smithery badge](https://smithery.ai/badge/@vbhjckfd/lad-lviv-ua)](https://smithery.ai/servers/vbhjckfd/lad-lviv-ua)
[![vbhjckfd/timetable-api-node MCP server](https://glama.ai/mcp/servers/vbhjckfd/timetable-api-node/badges/score.svg)](https://glama.ai/mcp/servers/vbhjckfd/timetable-api-node)

[![timetable-api-node MCP server](https://glama.ai/mcp/servers/vbhjckfd/timetable-api-node/badges/card.svg)](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

[![MCP server card](https://img.shields.io/badge/MCP-server_card-6366f1?style=flat-square)](https://api.lad.lviv.ua/.well-known/mcp/server-card.json)
[![REST stops.json](https://img.shields.io/badge/REST-stops.json-222?style=flat-square)](https://api.lad.lviv.ua/stops.json)
[![REST routes.json](https://img.shields.io/badge/REST-routes.json-222?style=flat-square)](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 &amp; 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 &amp; 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 &amp; 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"
  },
  "u
transittransporttransport-api

What people ask about timetable-api-node

What is vbhjckfd/timetable-api-node?

+

vbhjckfd/timetable-api-node is mcp servers for the Claude AI ecosystem. NodeJS version of lad.lviv.ua API It has 3 GitHub stars and its last recorded update is dated 2026-09-08.

How do I install timetable-api-node?

+

You can install timetable-api-node by cloning the repository (https://github.com/vbhjckfd/timetable-api-node) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is vbhjckfd/timetable-api-node safe to use?

+

Our security agent has analyzed vbhjckfd/timetable-api-node and assigned a Trust Score of 90/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains vbhjckfd/timetable-api-node?

+

vbhjckfd/timetable-api-node is maintained by vbhjckfd. The last recorded GitHub activity is dated 2026-09-08, with 0 open issues.

Are there alternatives to timetable-api-node?

+

Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.

Deploy timetable-api-node to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: vbhjckfd/timetable-api-node
[![Featured on ClaudeWave](https://claudewave.com/api/badge/vbhjckfd-timetable-api-node)](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>

More MCP Servers

timetable-api-node alternatives