Skip to main content
Glama
belgrano9

Renfe MCP Server

by belgrano9
README.md
# Renfe MCP Server ๐Ÿš„

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for querying **Renfe** (Spanish national railway) train schedules using official GTFS data. Integrates seamlessly with Claude Desktop and other MCP-compatible clients.

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![FastMCP](https://img.shields.io/badge/FastMCP-v0.7+-green.svg)](https://github.com/jlowin/fastmcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## โœจ Features

- ๐Ÿ” **Search trains** between any two Spanish cities on a specific date with pagination
- ๐Ÿ’ฐ **Check prices** - real-time price scraping from Renfe website with pagination support
- ๐Ÿš‰ **Find stations** in any city (Madrid has 7 stations!)
- ๐Ÿ“… **Flexible date parsing** - accepts ISO, European, and written date formats
- ๐Ÿ”„ **Auto-updates** - automatically downloads latest GTFS schedules from Renfe
- โšก **Fast & accurate** - uses official Renfe GTFS data with complete timetables
- ๐ŸŽฏ **Smart filtering** - handles service calendars, holidays, and exceptions
- ๐Ÿ› ๏ธ **Claude Desktop ready** - works out of the box with MCP clients

## ๐Ÿš€ Quick Start

### Prerequisites

- Python 3.12 or higher
- [uv](https://github.com/astral-sh/uv) (recommended) or pip

### Installation

1. **Clone the repository**
   ```bash
   git clone https://github.com/yourusername/renfe_mcp.git
   cd renfe_mcp
   ```

2. **Install dependencies**
   ```bash
   uv sync
   ```

3. **Run the server** (GTFS data downloads automatically)
   ```bash
   uv run python -m renfe_mcp.server
   ```

## ๐Ÿ“– Usage

### Standalone Testing

Test the search functionality directly:

```python
from renfe_mcp.schedule_searcher import ScheduleSearcher
from renfe_mcp.price_checker import check_prices

# Search trains
searcher = ScheduleSearcher("renfe_schedule")
trains = searcher.search("Madrid", "Barcelona", "2025-11-20")

# Check prices
prices = check_prices("Madrid", "Barcelona", "2025-11-20")
```

### Claude Desktop Integration

Add to your Claude Desktop config file:

**Windows** (`%APPDATA%\Claude\claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "renfe": {
      "command": "uv",
      "args": [
        "--directory",
        "C:\\Users\\YourName\\path\\to\\renfe_mcp",
        "run",
        "python",
        "-m",
        "renfe_mcp.server"
      ]
    }
  }
}
```

**macOS** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "renfe": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/renfe_mcp",
        "run",
        "python",
        "-m",
        "renfe_mcp.server"
      ]
    }
  }
}
```

**Linux** (`~/.config/Claude/claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "renfe": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/renfe_mcp",
        "run",
        "python",
        "-m",
        "renfe_mcp.server"
      ]
    }
  }
}
```

Restart Claude Desktop, and you can ask:
- *"Show me trains from Madrid to Barcelona tomorrow"*
- *"What's the earliest train from Barcelona to Valencia on December 1st?"*
- *"What train stations are in Madrid?"*
- *"How many trains run between Madrid and Sevilla in the afternoon?"*
- *"Check prices for trains from Madrid to Barcelona on December 1st"*
- *"What are the ticket prices for the first 5 trains?"*

## ๐Ÿ› ๏ธ MCP Tools

### 1. `search_trains`

Find trains between two cities on a specific date with pagination support.

**Parameters:**
- `origin` (string): Origin city name (e.g., "Madrid", "Barcelona")
- `destination` (string): Destination city name (e.g., "Valencia", "Sevilla")
- `date` (string, optional): Travel date in flexible formats:
  - ISO: `"2025-11-28"`
  - European: `"28/11/2025"`
  - Written: `"November 28, 2025"`
  - Default: today's date
- `page` (integer, optional): Page number to display (default: 1)
- `per_page` (integer, optional): Results per page (default: 10, max: 50)

**Example Output:**
```
Found 36 train(s) total
Showing page 1 of 4 (10 trains)

  1. AVE
     Madrid Pta.Atocha - Almudena Grandes โ†’ Barcelona-Sants
     Departs: 6:16:00 | Arrives: 9:05:00
     Duration: 2h 49min

  2. AVE
     Madrid Pta.Atocha - Almudena Grandes โ†’ Barcelona-Sants
     Departs: 6:27:00 | Arrives: 9:25:00
     Duration: 2h 58min
  ...

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
To see more trains, use page=2
Total pages: 4
```

### 2. `find_station`

Search for train stations in a city.

**Parameters:**
- `city_name` (string): City name to search (e.g., "Madrid")

**Example Output:**
```
Found 7 stations:

All stations found:
  1. Madrid-Chamartรญn-Clara Campoamor (ID: 17000)
  2. Madrid - Atocha Cercanรญas (ID: 18000)
  3. Madrid Pta.Atocha - Almudena Grandes (ID: 60000)
  ...
```

### 3. `get_train_prices`

Check actual ticket prices by scraping the Renfe website with pagination support.

**Parameters:**
- `origin` (string): Origin city name (e.g., "Madrid", "Barcelona")
- `destination` (string): Destination city name (e.g., "Valencia", "Sevilla")
- `date` (string, optional): Travel date (same formats as `search_trains`)
- `page` (integer, optional): Page number to display (default: 1)
- `per_page` (integer, optional): Results per page (default: 5, max: 20)

**Example Output:**
```
PRICE CHECK RESULTS
From: Madrid -> Barcelona
Date: 2025-11-17
Showing 5 train(s)

  1. AVE
     Departs: 06:16 | Arrives: 09:05
     Duration: 2h 49min
     Price: 94.90 EUR | [Available]

  2. AVE
     Departs: 06:27 | Arrives: 09:25
     Duration: 2h 58min
     Price: 118.60 EUR | [Available]
  ...

To see more prices, try page=2
```

**Note:** This tool scrapes the Renfe website and may take a few seconds to complete. Pagination now matches `search_trains` so you can get prices for trains on any page (e.g., page 2 shows prices for trains 6-10).

## ๐Ÿ“Š Data Updates

The server includes automatic GTFS data updates from Renfe's open data portal.

### Automatic Updates

On server startup, it checks for new data and downloads if needed:

```bash
uv run python -m renfe_mcp.server
# [CHECK] Checking data versions:
#         Server: 2025-11-15T00:40:21
#         Local:  2025-11-10T00:30:15
# [UPDATE] Server has newer data
# [DOWNLOAD] Downloading GTFS data...
# [OK] GTFS data updated successfully!
```

### Manual Updates

**Check and update if needed:**
```bash
uv run python -m renfe_mcp.update_data
```

**Force update (download regardless of version):**
```bash
uv run python -m renfe_mcp.update_data --force
```

The update system:
- โœ… Compares server version with local version
- โœ… Only downloads when new data is available
- โœ… Stores version info in `renfe_schedule/.last_updated`
- โœ… Automatically extracts GTFS CSV files

## ๐Ÿ—๏ธ Architecture

```
renfe_mcp/
โ”œโ”€โ”€ pyproject.toml           # Dependencies & build config
โ”œโ”€โ”€ README.md                # This file
โ”œโ”€โ”€ src/renfe_mcp/           # Main package
โ”‚   โ”œโ”€โ”€ __init__.py          # Package exports
โ”‚   โ”œโ”€โ”€ server.py            # FastMCP server implementation
โ”‚   โ”œโ”€โ”€ config.py            # Pydantic configuration
โ”‚   โ”œโ”€โ”€ exceptions.py        # Exception hierarchy
โ”‚   โ”œโ”€โ”€ logging.py           # Structured logging
โ”‚   โ”œโ”€โ”€ security.py          # Auth & rate limiting
โ”‚   โ”œโ”€โ”€ price_checker.py     # Price checking module
โ”‚   โ”œโ”€โ”€ schedule_searcher.py # GTFS schedule search
โ”‚   โ”œโ”€โ”€ station_service.py   # Unified station lookups
โ”‚   โ”œโ”€โ”€ update_data.py       # GTFS data updater
โ”‚   โ””โ”€โ”€ scraper/             # Price scraper package
โ”‚       โ”œโ”€โ”€ __init__.py      # Package exports
โ”‚       โ”œโ”€โ”€ scraper.py       # RenfeScraper with DWR protocol
โ”‚       โ”œโ”€โ”€ dwr.py           # DWR utilities
โ”‚       โ”œโ”€โ”€ models.py        # Pydantic models
โ”‚       โ”œโ”€โ”€ exceptions.py    # Scraper exceptions
โ”‚       โ””โ”€โ”€ stations.json    # Station code database
โ”œโ”€โ”€ tests/                   # Test suite
โ”‚   โ”œโ”€โ”€ test_final_integration.py
โ”‚   โ”œโ”€โ”€ test_security.py
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ renfe_schedule/          # GTFS data (auto-downloaded)
    โ”œโ”€โ”€ stops.txt            # Station information
    โ”œโ”€โ”€ routes.txt           # Train routes
    โ”œโ”€โ”€ trips.txt            # Trip schedules
    โ”œโ”€โ”€ stop_times.txt       # Arrival/departure times
    โ”œโ”€โ”€ calendar.txt         # Service schedules
    โ”œโ”€โ”€ calendar_dates.txt   # Holiday exceptions
    โ””โ”€โ”€ .last_updated        # Version tracking
```

### How It Works

1. **City to Station Mapping**: Fuzzy matches city names to station IDs
2. **Date Filtering**:
   - Checks `calendar.txt` for service schedules (day of week)
   - Applies exceptions from `calendar_dates.txt` (holidays, special dates)
3. **Route Finding**:
   - Joins trips, stop_times, and stops tables
   - Validates stop sequences and pickup/dropoff permissions
   - Ensures origin comes before destination
4. **Results**: Returns all trains sorted chronologically with proper numeric time sorting

### Key Implementation Details

- โœ… Handles multiple stations per city (e.g., Madrid has 7)
- โœ… Respects service exceptions (holidays, maintenance)
- โœ… Validates passenger boarding/alighting permissions (`pickup_type`, `drop_off_type`)
- โœ… **Fixed sorting bug**: Proper numeric time sorting (not lexicographic!)
- โœ… CSV column whitespace handling (strips on load)
- โœ… Complete result sets (no truncation)

## ๐Ÿ—บ๏ธ Supported Routes

The server supports **any route** in the Renfe network:

- **High-Speed (AVE)**: Madrid-Barcelona, Madrid-Sevilla, Madrid-Valencia
- **Long-Distance (ALVIA, Intercity)**: Major city connections
- **International (AVE INT)**: Cross-border services
- **Regional**: Local routes across Spain
- **Commuter (Cercanรญas)**: Urban networks

**Popular Cities:**
- Madrid (7 stations), Barcelona, Valencia
- Sevilla, Mรกlaga, Bilbao, Zaragoza
- Alicante, Cรณrdoba, Granada, Murcia
- **100+ cities** across Spain!

Use `find_station` to discover available stations in any city.

## ๐Ÿ”ง Development

### Project Setup

```bash
# Clone and install
git clone https://github.com/yourusername/renfe_mcp.git
cd renfe_mcp
uv sync

# Run the server
uv run python -m renfe_mcp.server

# Run tests
uv run python tests/test_final_integration.py
```

### Dependencies

- **fastmcp** (>=0.7.0) - MCP server framework
- **pandas** (>=2.3.3) - GTFS data processing
- **pydantic** (>=2.11.7) - Data validation and models
- **pydantic-settings** (>=2.0.0) - Environment-based configuration
- **httpx** (>=0.27.0) - Modern HTTP client for price scraping
- **python-dateutil** (>=2.8.2) - Flexible date parsing
- **json5** (>=0.12.0) - JavaScript object parsing for DWR responses
- **python-dotenv** (>=1.0.0) - Environment variables

### Configuration

Configure via environment variables (prefix `RENFE_`) or `.env` file:

```bash
# Authentication
RENFE_ENABLE_AUTH=true
RENFE_API_KEY=your-secret-key

# Rate Limiting
RENFE_RATE_LIMIT_ENABLED=true
RENFE_MAX_REQUESTS_PER_MINUTE=30
RENFE_MAX_REQUESTS_PER_HOUR=200

# Development
RENFE_DEV_MODE=false
RENFE_LOG_LEVEL=INFO
```

## ๐Ÿ“ Data Source

GTFS data from [Renfe's Open Data Portal](https://data.renfe.com):
- **API Endpoint**: `https://data.renfe.com/api/3/action/resource_show`
- **Resource ID**: `25d6b043-9e47-4f99-bd91-edd51d782450`
- **Update Frequency**: Updated regularly by Renfe (checked on server startup)
- **Format**: GTFS (General Transit Feed Specification)
- **Size**: ~800 KB compressed, ~40 MB extracted

## ๐Ÿ› Known Issues

- Windows console may show encoding errors with Unicode characters (functionality not affected)
- Very large result sets (100+ trains) can be verbose but complete

## ๐Ÿค Contributing

Contributions welcome! Please:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Ideas for Contributions

- [x] ~~Add price information~~ (โœ… Implemented via web scraping)
- [ ] Support for train status/delays
- [ ] Multi-leg journey planning
- [ ] Visualization of routes on maps
- [ ] Additional query filters (train type, duration, etc.)
- [ ] Return trip price checking

## ๐Ÿ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## ๐Ÿ™ Acknowledgments

- [Renfe Operadora](https://www.renfe.com) for providing open GTFS data
- [FastMCP](https://github.com/jlowin/fastmcp) by [@jlowin](https://github.com/jlowin) for the excellent MCP framework
- [Anthropic](https://www.anthropic.com) for Claude and the Model Context Protocol
- The GTFS community for standardizing transit data

## ๐Ÿ“ฎ Support

- **Issues**: [GitHub Issues](https://github.com/belgrano9/renfe_mcp_server/issues)
- **Discussions**: [GitHub Discussions](https://github.com/belgrano9/renfe_mcp_server/discussions)
- **MCP Docs**: [Model Context Protocol](https://modelcontextprotocol.io)

## ๐Ÿ”— Related Projects

- [SNCF MCP Server](https://github.com/belgrano9/sncf_mcp_server) - Similar server for French railways
- [FastMCP](https://github.com/jlowin/fastmcp) - The framework powering this server
- [MCP Servers](https://github.com/modelcontextprotocol/servers) - Official MCP server implementations

## ๐Ÿ™ Sources & Inspiration

- **[renfe-bot](https://github.com/emartinez-dev/renfe-bot)** by [@emartinez-dev](https://github.com/emartinez-dev) - Telegram bot for Renfe train ticket monitoring. The DWR (Direct Web Remoting) protocol reverse-engineering and price scraping techniques were inspired by renfe-bot's implementation. This MCP server features a custom-built scraper using modern Python (httpx, pydantic) while preserving the core DWR protocol logic.

---

**Built with โค๏ธ using [FastMCP](https://github.com/jlowin/fastmcp) and [Claude](https://claude.ai)**

*Travel smart, travel by train! ๐Ÿš„*

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a distinct purpose: station lookup, journey search, and price retrieval. No overlap in functionality.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern in snake_case: find_station, get_train_prices, search_trains.

Tool Count4/5

Three tools cover the core workflow of finding stations, searching journeys, and checking prices. While minimal, it's appropriate for the scope.

Completeness4/5

Covers the essential travel information needs: station lookup, journey search, and pricing. Missing booking or detailed trip info, but acceptable for an info-focused server.

Maintenance

ActivityInactive
ResponsivenessNo issues