Metadata-Version: 2.4
Name: mcp-server-public-transport
Version: 0.3.0
Summary: An MCP-compatible server for querying public transport data in Europe (UK, CH, BE, NO, Berlin/Brandenburg, PT).
Author-email: Miro Duman <dumanmiro@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/mirodn/mcp-server-public-transport
Project-URL: Repository, https://github.com/mirodn/mcp-server-public-transport
Keywords: mcp,public transport,api,europe,fastmcp
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.12.14
Requires-Dist: certifi>=2025.7.14
Requires-Dist: fastmcp>=2.10.6
Requires-Dist: python-dotenv>=1.1.1
Requires-Dist: typing-extensions>=4.8
Requires-Dist: pydantic>=2.6
Provides-Extra: dev
Requires-Dist: pytest>=8.4.1; extra == "dev"
Requires-Dist: pytest-asyncio>=1.1.0; extra == "dev"
Requires-Dist: ruff>=0.4.5; extra == "dev"
Dynamic: license-file

# mcp-server-public-transport

[![LightNow capabilities](https://lightnow.ai/badge/io.github.mirodn/mcp-server-public-transport)](https://lightnow.ai/servers/io.github.mirodn/mcp-server-public-transport)

An MCP Server providing real-time public transport data across Europe.

<a href="https://glama.ai/mcp/servers/@mirodn/mcp-server-public-transport">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@mirodn/mcp-server-public-transport/badge" />
</a>

## About

mcp-server-public-transport is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction)-compatible local server that provides access to public transport data across Europe.
Currently, it integrates APIs from UK, Switzerland, Norway, Belgium, Berlin/Brandenburg and Portugal (Lisbon + Porto), allowing you to retrieve train connections, live departures, and bus locations.

## Feature Implementation Status

### Supported Countries

| Country               | API Base URL                                                     | Status |
| --------------------- | ---------------------------------------------------------------- | ------ |
| **United Kingdom**    | [https://transportapi.com](https://transportapi.com)             | ✅  |
| **Switzerland**       | [https://transport.opendata.ch](https://transport.opendata.ch)   | ✅     |
| **Belgium**           | [https://api.irail.be](https://api.irail.be)                      | ✅     |
| **Norway**            | [https://api.entur.io](https://api.entur.io)                     | ✅    |
| **Berlin/Brandenburg**| [https://v6.vbb.transport.rest](https://v6.vbb.transport.rest)   | ✅    |
| **Portugal** (Lisbon + Porto) | [https://api.transitous.org](https://api.transitous.org)      | ✅    |

### Features by Country

| Feature               | API Path                                                        | Status |
| --------------------- | --------------------------------------------------------------- | ------ |
| **United Kingdom** |   |   |
| Live Departures | `/uk/train/station_timetables/{station_code}.json`        | ✅ |
| **Switzerland** | | |
| Search Connections | `/connections`                    | ✅     |
| Station Lookup     | `/locations`                      | ✅     |
| Departure Board    | `/stationboard`                   | ✅     |
| Nearby Stations    | `/locations?x={lon}&y={lat}`      | ✅     |
| **Belgium**           |                                |        |
| Live Departures | `/departures`                   | ✅     |
| Station Lookup     | `/stations`                     | ✅     |
| Nearby Stations    | `/stations/nearby`              | ✅     |
| **Norway**            |                                |        |
| Place Search |`/geocoder/v1/autocomplete`| ✅ |
| Live Departures| `GraphQL: stopPlace(id) { estimatedCalls(...) }`|✅|
|Trip Planning| `GraphQL: trip(from, to, dateTime, numTripPatterns, ...)`	|✅|
|Nearest Stops|`GraphQL: nearest(latitude, longitude, maximumDistance, ...)`|	✅|
| **Berlin/Brandenburg** | | |
| Location Search | `/locations` | ✅ |
| Live Departures | `/stops/:id/departures` | ✅ |
| Live Arrivals | `/stops/:id/arrivals` | ✅ |
| Journey Planning | `/journeys` | ✅ |
| Nearby Stations | `/locations/nearby` | ✅ |
| **Portugal** (Lisbon + Porto) | | |
| Station Lookup | `/geocode` | ✅ |
| Search Connections | `/plan` | ✅ |
| Departure Board | `/stoptimes` | ✅ |
| Nearby Stations | `/reverse-geocode` | ✅ |

> Portugal coverage is scoped to the Lisbon and Porto metro areas (Metro Lisboa, Carris, Carris Metropolitana, CP suburban; Metro do Porto, STCP) via [Transitous](https://transitous.org), a free MOTIS instance over ingested GTFS feeds. No API key required.

#### Attribution (Transitous)

Lisbon/Porto data is served by [Transitous](https://transitous.org), a volunteer-run,
best-effort service (limited hosting, no availability or accuracy guarantees). Keep the
Transitous attribution visible when showing this data to users; underlying feeds, including
OpenStreetMap, are listed at [transitous.org/sources](https://transitous.org/sources/).

Follow the [Transitous usage policy](https://transitous.org/api/): modest request volume,
an identifying `User-Agent`, non-commercial / open-source use only.

## Response Format

Departure/arrival boards and connection searches return a compact format that is the
same for every country, instead of the raw upstream payload (typically 80–95% smaller):

```json
{
  "station": {"id": "8503000", "name": "Zürich HB"},
  "departures": [
    {"line": "S 14", "category": "S", "destination": "Hinwil",
     "planned": "2026-10-05T10:12:00+02:00", "expected": "2026-10-05T10:36:00+02:00",
     "delay_min": 24, "platform": "33", "operator": "SBB"}
  ]
}
```

- Times are ISO 8601 in local time. `expected` is only set when real-time data exists.
- Journeys contain `legs` (with `walk: true` for footpaths); `departure`, `arrival` and
  `duration_min` of a journey are scheduled values, real-time data is on the legs.
- Disruption messages appear in `remarks`. Empty fields are omitted.
- Pass `raw: true` to get the unmodified upstream response.

Station searches, nearby lookups, `be_get_vehicle` and `uk_live_departures` still return
the upstream payload.

## Setup

### Hosted Server (no install)

A public instance is available via Streamable HTTP:

```plaintext
https://mcp-public-transport-412652413053.europe-west1.run.app/mcp
```

- **Claude (web / desktop):** Settings → Connectors → Add custom connector, paste the URL.
- **Claude Code:** `claude mcp add --transport http public-transport https://mcp-public-transport-412652413053.europe-west1.run.app/mcp`

The hosted instance does not include UK tools. For UK data, run the server locally with your own keys (see below).

### Environment Variables

Set the following environment variables:

```plaintext
UK_TRANSPORT_APP_ID=your_uk_app_id
UK_TRANSPORT_API_KEY=your_uk_api_key
```

### Usage with Claude Desktop

The quickest way is to run the published package with `uvx` (no clone needed).
Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "mcp-server-public-transport": {
      "command": "uvx",
      "args": ["mcp-server-public-transport"],
      "env": {
        "UK_TRANSPORT_APP_ID": "your-uk-app-id",
        "UK_TRANSPORT_API_KEY": "your-uk-api-key"
      }
    }
  }
}
```

To run from a local clone instead:

```json
{
  "mcpServers": {
    "mcp-server-public-transport": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/mcp-server-public-transport",
        "run",
        "server.py"
      ],
      "env": {
        "UK_TRANSPORT_APP_ID": "your-uk-app-id",
        "UK_TRANSPORT_API_KEY": "your-uk-api-key"
      }
    }
  }
}

```

Replace `/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-server-public-transport` with the actual path where you've cloned the repository.
> Note: You may need to put the full path to the uv executable in the command field. You can get this by running which uv on MacOS/Linux or where uv on Windows.

## Development

### Setting up Development Environment

1. **Clone the repository**

   ```bash
   git clone https://github.com/mirodn/mcp-server-public-transport.git
   cd mcp-server-public-transport
    ```

2. **Install dependencies**

    ```bash
    uv sync
    ```

3. **Set environment variables**

    ```bash
    cp env.example .env
    ```

4. **Run the server**

    ```bash
    uv run server.py
    ```

### Running Tests

The project uses pytest for testing with the following commands available:

```bash
# Run all tests
make test
```

### Code Quality

```bash
# Run linting
make lint

# Run code formatting
make format
```

### Continuous Integration

The project includes a GitHub Actions workflow (`.github/workflows/test.yml`) that automatically:

- Runs tests on Python 3.10, 3.11, and 3.12
- Executes linting checks using ruff
- Runs on every push and pull request to `main` branch

The CI pipeline ensures code quality and compatibility across supported Python versions before any changes are merged.

### Hosting (Google Cloud Run)

The hosted instance runs on Cloud Run with stateless Streamable HTTP (`MCP_STATELESS=true`),
scale-to-zero and a single max instance. A monthly budget triggers
[`deploy/budget-guard`](deploy/budget-guard/main.py), which makes the service private
once the budget is used up.

The hosted instance has no UK credentials, so UK tools are not available there. To use them,
get your own key at [transportapi.com](https://transportapi.com) and run the server locally (see [Setup](#setup)).

- One-time setup: [`deploy/gcp-setup.sh`](deploy/gcp-setup.sh)
- Deploys: [`.github/workflows/deploy-cloudrun.yml`](.github/workflows/deploy-cloudrun.yml)
  runs on release tags or manually. It only swaps the image, so limits and public access stay as configured.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

The package is deployed automatically to PyPI when project.version is updated in `pyproject.toml`.
Follow semver for versioning.

## License

[MIT License](LICENSE)
