SNCF API MCP Server
by Kryzo
Verified
- mcp-sncf
- sncf_api
from typing import Dict, Any, List, Optional
import re
from .client import SNCFClient
from .config import DEFAULT_COUNT, DEFAULT_DEPTH
class DisruptionsAPI:
"""
Disruption-related API endpoints
"""
def __init__(self, client: SNCFClient):
"""
Initialize with a SNCF API client
"""
self.client = client
def get_disruptions(
self,
coverage: str = "sncf",
count: int = DEFAULT_COUNT,
depth: int = DEFAULT_DEPTH,
datetime: Optional[str] = None,
forbidden_uris: Optional[List[str]] = None,
since: Optional[str] = None,
until: Optional[str] = None,
filter: Optional[str] = None,
tags: Optional[List[str]] = None,
fetch_train_details: bool = True,
station_id: Optional[str] = None,
line_id: Optional[str] = None,
debug: bool = False
) -> List[Dict[str, Any]]:
"""
Check for current disruptions in the transport network
Args:
coverage: The coverage area
count: Maximum number of disruptions to return
depth: Level of detail in the response (0-3)
datetime: Date and time in format YYYYMMDDTHHMMSS
forbidden_uris: List of URIs to exclude from the disruptions
since: Only disruptions valid after this date (format YYYYMMDDTHHMMSS)
until: Only disruptions valid before this date (format YYYYMMDDTHHMMSS)
filter: Filter disruptions by a specific field
tags: List of tags to filter disruptions
fetch_train_details: Whether to include detailed information about stops and schedules
station_id: Filter disruptions affecting a specific station
line_id: Filter disruptions affecting a specific line
debug: Whether to include the raw response in the output
Returns:
A list of current disruptions with detailed information
"""
params = {
"count": count,
"depth": depth,
"_current_datetime": datetime
}
if forbidden_uris:
params.update(self.client._format_list_param("forbidden_uris", forbidden_uris))
if since:
params["since"] = since
if until:
params["until"] = until
if filter:
params["filter"] = filter
if tags:
params.update(self.client._format_list_param("tags", tags))
if station_id:
params["filter"] = f"stop_point.id={station_id}"
if line_id:
params["filter"] = f"line.id={line_id}"
endpoint = f"coverage/{coverage}/disruptions"
data = self.client._make_request(endpoint, params)
disruptions = []
if "disruptions" in data:
for disruption in data["disruptions"]:
disruption_info = {
"id": disruption.get("id", ""),
"disruption_id": disruption.get("disruption_id", ""),
"status": disruption.get("status", ""),
"impact_id": disruption.get("impact_id", ""),
"cause": disruption.get("cause", ""),
"updated_at": disruption.get("updated_at", ""),
"severity": {
"name": disruption.get("severity", {}).get("name", ""),
"effect": disruption.get("severity", {}).get("effect", ""),
"color": disruption.get("severity", {}).get("color", ""),
"priority": disruption.get("severity", {}).get("priority", 0)
},
"application_periods": [],
"messages": [],
}
# Extract application periods
for period in disruption.get("application_periods", []):
disruption_info["application_periods"].append({
"begin": period.get("begin", ""),
"end": period.get("end", "")
})
# Extract messages
for message in disruption.get("messages", []):
if "text" in message:
message_info = {
"text": message["text"],
"channel": {
"name": message.get("channel", {}).get("name", ""),
"types": message.get("channel", {}).get("types", [])
}
}
disruption_info["messages"].append(message_info)
# Extract detailed information about impacted objects and stops
impacted_objects = []
for impacted_object in disruption.get("impacted_objects", []):
if "pt_object" in impacted_object:
pt_object = impacted_object["pt_object"]
embedded_type = pt_object.get("embedded_type", "")
impacted_info = {
"id": pt_object.get("id", ""),
"name": pt_object.get("name", ""),
"type": embedded_type,
}
# Add more details based on the embedded type
if embedded_type in pt_object:
impacted_info[embedded_type] = pt_object[embedded_type]
# Add stop information if available and fetch_train_details is True
if fetch_train_details and "impacted_stops" in impacted_object:
stops_info = []
# Track origin and destination
origin_stop = None
destination_stop = None
for i, stop in enumerate(impacted_object.get("impacted_stops", [])):
stop_info = {
"stop_name": stop.get("stop_point", {}).get("name", ""),
"stop_id": stop.get("stop_point", {}).get("id", ""),
"city": self._extract_city_from_label(stop.get("stop_point", {}).get("label", "")),
"coordinates": {
"lon": stop.get("stop_point", {}).get("coord", {}).get("lon", ""),
"lat": stop.get("stop_point", {}).get("coord", {}).get("lat", "")
},
"base_arrival_time": self._format_time(stop.get("base_arrival_time", "")),
"base_departure_time": self._format_time(stop.get("base_departure_time", "")),
"amended_arrival_time": self._format_time(stop.get("amended_arrival_time", "")),
"amended_departure_time": self._format_time(stop.get("amended_departure_time", "")),
"cause": stop.get("cause", ""),
"stop_time_effect": stop.get("stop_time_effect", ""),
"arrival_status": stop.get("arrival_status", ""),
"departure_status": stop.get("departure_status", ""),
"is_detour": stop.get("is_detour", False)
}
# Track first stop as origin
if i == 0:
origin_stop = stop_info
# Update destination with each stop, so the last one becomes the final destination
destination_stop = stop_info
stops_info.append(stop_info)
impacted_info["stops"] = stops_info
# Add origin and destination info for easier access
if origin_stop:
impacted_info["origin"] = {
"stop_name": origin_stop["stop_name"],
"city": origin_stop["city"],
"base_departure_time": origin_stop["base_departure_time"],
"amended_departure_time": origin_stop["amended_departure_time"]
}
if destination_stop:
impacted_info["destination"] = {
"stop_name": destination_stop["stop_name"],
"city": destination_stop["city"],
"base_arrival_time": destination_stop["base_arrival_time"],
"amended_arrival_time": destination_stop["amended_arrival_time"]
}
# Calculate disruption effects summary
impacted_info["disruption_effects"] = self._calculate_disruption_effects(stops_info)
impacted_objects.append(impacted_info)
disruption_info["impacted_objects"] = impacted_objects
# Add raw data if debug is True
if debug:
disruption_info["raw_data"] = disruption
disruptions.append(disruption_info)
return disruptions
def _extract_city_from_label(self, label: str) -> str:
"""
Extract city name from a label in format "Station Name (City Name)"
Args:
label: The label string to parse
Returns:
The extracted city name or empty string if not found
"""
if not label:
return ""
# Try to extract city from parentheses
match = re.search(r'\((.*?)\)', label)
if match:
return match.group(1)
return ""
def _format_time(self, time_str: str) -> str:
"""
Format time string from HHMMSS to HH:MM:SS
Args:
time_str: Time string in format HHMMSS
Returns:
Formatted time string HH:MM:SS or empty string if invalid
"""
if not time_str or len(time_str) < 6:
return ""
try:
# Extract hours, minutes, seconds
hours = time_str[:2]
minutes = time_str[2:4]
seconds = time_str[4:6]
return f"{hours}:{minutes}:{seconds}"
except Exception:
return time_str
def _calculate_disruption_effects(self, stops: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Calculate disruption effects summary from stop information
Args:
stops: List of stop information dictionaries
Returns:
Dictionary with disruption effect summary
"""
effects = {
"has_cancellations": False,
"has_delays": False,
"has_added_stops": False,
"has_detours": False,
"total_delay_minutes": 0,
"affected_stops": []
}
for stop in stops:
# Check for cancellations
if stop["stop_time_effect"] == "deleted":
effects["has_cancellations"] = True
effects["affected_stops"].append({
"stop_name": stop["stop_name"],
"city": stop["city"],
"effect": "cancelled"
})
# Check for added stops
elif stop["stop_time_effect"] == "added":
effects["has_added_stops"] = True
effects["affected_stops"].append({
"stop_name": stop["stop_name"],
"city": stop["city"],
"effect": "added"
})
# Check for delays
if stop["base_arrival_time"] and stop["amended_arrival_time"] and stop["base_arrival_time"] != stop["amended_arrival_time"]:
effects["has_delays"] = True
# Calculate delay in minutes
base_arr = stop["base_arrival_time"].replace(":", "")
amended_arr = stop["amended_arrival_time"].replace(":", "")
try:
# Very simplified delay calculation (assumes same day)
base_minutes = int(base_arr[:2]) * 60 + int(base_arr[2:4])
amended_minutes = int(amended_arr[:2]) * 60 + int(amended_arr[2:4])
delay = amended_minutes - base_minutes
# Handle negative delay (possible for early arrivals)
if delay > 0:
effects["total_delay_minutes"] += delay
effects["affected_stops"].append({
"stop_name": stop["stop_name"],
"city": stop["city"],
"effect": "delayed",
"delay_minutes": delay
})
except Exception:
pass
# Check for detours
if stop["is_detour"]:
effects["has_detours"] = True
effects["affected_stops"].append({
"stop_name": stop["stop_name"],
"city": stop["city"],
"effect": "detour"
})
return effects