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.
[](https://www.python.org/downloads/)
[](https://github.com/jlowin/fastmcp)
[](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