f1-mcp-server / app.py
arre99's picture
added video URL to README. minor touch ups
5af4f26
import gradio as gr
# Local modules
import fastf1_tools
import openf1_tools
from utils.constants import (
DRIVER_NAMES,
CONSTRUCTOR_NAMES,
CURRENT_YEAR,
DROPDOWN_SESSION_TYPES,
MARKDOWN_INTRODUCTION,
HTML_INTRODUCTION,
MARKDOWN_OPENF1_EXAMPLES,
OPENF1_TOOL_DESCRIPTION,
CONSTRUCTORS_PER_SEASON,
DRIVERS_PER_SEASON
)
with gr.Blocks() as iface_driver_championship_standings:
gr.Markdown("## World Driver Championship Standings\nGet the world driver championship standings for a specific driver. Note that the older data has gaps and may not be entirely complete.")
with gr.Row():
year_input = gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR)
driver_dropdown = gr.Dropdown(label="Driver", choices=DRIVERS_PER_SEASON.get(str(year_input.value), []))
output_text = gr.Textbox(label="Result")
submit_btn = gr.Button("Submit")
def update_drivers(year):
choices = DRIVERS_PER_SEASON.get(str(year), [])
return gr.update(choices=choices, value=(choices[0] if choices else None))
year_input.blur(
update_drivers,
inputs=year_input,
outputs=driver_dropdown
)
submit_btn.click(
fastf1_tools.driver_championship_standings,
inputs=[year_input, driver_dropdown],
outputs=output_text
)
with gr.Blocks() as iface_constructor_championship_standings:
gr.Markdown("## World Constructor Championship Standings\nGet the current/past world constructor championship standings for a specific constructor. Note that the older data has gaps and may not be entirely complete.")
with gr.Row():
year_input = gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR)
constructor_dropdown = gr.Dropdown(label="Constructor", choices=CONSTRUCTORS_PER_SEASON.get(str(year_input.value), []))
output_text = gr.Textbox(label="Result")
submit_btn = gr.Button("Submit")
def update_constructors(year):
choices = CONSTRUCTORS_PER_SEASON.get(str(year), [])
return gr.update(choices=choices, value=(choices[0] if choices else None))
year_input.blur(
update_constructors,
inputs=year_input,
outputs=constructor_dropdown
)
submit_btn.click(
fastf1_tools.constructor_championship_standings,
inputs=[year_input, constructor_dropdown],
outputs=output_text
)
iface_event_info = gr.Interface(
fn=fastf1_tools.get_event_info,
inputs=[
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
gr.Radio(["human", "LLM"], label="Display format", value="human", info="Toggle between human-readable (parsed) and LLM output (raw)")
],
outputs="text",
title="Event Info",
description="Get information about a specific Grand Prix event. Example: (2025,Monaco,human)"
)
iface_season_calendar = gr.Interface(
fn=fastf1_tools.get_season_calendar,
inputs=[
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
],
outputs="text",
title="Season Calendar",
description="Get the season calendar for the given year"
)
iface_track_visualization = gr.Interface(
fn=fastf1_tools.track_visualization,
inputs=[
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
gr.Radio(["speed", "corners", "gear"], label="Visualization type", value="speed", info="What type of track visualization to generate"),
],
outputs="image",
title="Track Visualizations",
description="Get the track visualization (speed/corners/gear) for the fastest lap at the specific Grand Prix race. Example: (2025,Monaco,speed)"
)
iface_session_results = gr.Interface(
fn=fastf1_tools.get_session_results,
inputs=[
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
gr.Dropdown([session_type for session_type in DROPDOWN_SESSION_TYPES if "practice" not in session_type], label="Session type", value="race", info="The session type to get results for. Dataframe's columns vary depending on session type.")
],
outputs=gr.Dataframe(
headers=None, # Let it infer from returned DataFrame
row_count=(0, "dynamic"), # Start empty, allow it to grow
col_count=(0, "dynamic") # Let columns adjust too
),
title="Session Results",
description="Get the session results for the given Grand Prix. Example: (2025,Monaco,qualifying)"
)
iface_driver_info = gr.Interface(
fn=fastf1_tools.get_driver_info,
inputs=[
gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
],
outputs="text",
title="Driver Info",
description="Get background information about a specific driver from the 2025 Formula 1 season"
)
iface_constructor_info = gr.Interface(
fn=fastf1_tools.get_constructor_info,
inputs=[
gr.Dropdown(label="Constructor", choices=CONSTRUCTOR_NAMES)
],
outputs="text",
title="Constructor Info",
description="Get background information about a specific constructor from the 2025 Formula 1 season"
)
# About introduction tab
with gr.Blocks() as markdown_tab:
gr.HTML(HTML_INTRODUCTION)
# OpenF1 tools tab
def openf1_tools_tab():
with gr.Blocks() as openf1_tools_tab:
gr.Markdown(OPENF1_TOOL_DESCRIPTION)
with gr.Accordion("get_api_endpoints()", open=False):
btn = gr.Button("Get all endpoints")
output = gr.JSON()
btn.click(openf1_tools.get_api_endpoints, outputs=output)
with gr.Accordion("get_api_endpoint(endpoint)", open=False):
endpoint_in = gr.Textbox(label="Endpoint", placeholder="e.g. sessions")
btn = gr.Button("Get endpoint info")
output = gr.JSON()
btn.click(openf1_tools.get_api_endpoint, inputs=endpoint_in, outputs=output)
with gr.Accordion("get_endpoint_info(endpoint)", open=False):
endpoint_in = gr.Textbox(label="Endpoint", placeholder="e.g. sessions")
btn = gr.Button("Get endpoint details")
output = gr.JSON()
btn.click(openf1_tools.get_endpoint_info, inputs=endpoint_in, outputs=output)
with gr.Accordion("get_filter_info(filter_name)", open=False):
filter_in = gr.Textbox(label="Filter name", placeholder="e.g. driver_number")
btn = gr.Button("Get filter info")
output = gr.JSON()
btn.click(openf1_tools.get_filter_info, inputs=filter_in, outputs=output)
with gr.Accordion("get_filter_string(filter_name, filter_value, operator)", open=False):
filter_name = gr.Textbox(label="Filter name", placeholder="e.g. driver_number")
filter_value = gr.Textbox(label="Filter value", placeholder="e.g. 16")
operator = gr.Dropdown(label="Operator", choices=["=", ">", "<", ">=", "<="], value="=")
btn = gr.Button("Get filter string")
output = gr.Textbox(label="Filter string", info="Example: driver_number=16&")
btn.click(openf1_tools.get_filter_string, inputs=[filter_name, filter_value, operator], outputs=output)
with gr.Accordion("apply_filters(api_string, *filters)", open=False):
api_string = gr.Textbox(label="Base API string", placeholder="e.g. https://api.openf1.org/v1/sessions?")
filters = gr.Textbox(label="Filters (comma-separated)", placeholder="e.g. driver_number=16&,session_key=123&")
btn = gr.Button("Apply filters")
output = gr.Textbox(label="Full API string")
btn.click(openf1_tools.apply_filters, inputs=[api_string, filters], outputs=output)
with gr.Accordion("send_request(api_string)", open=False):
with gr.Accordion("Example API requests (copy & paste into text box below)", open=False):
gr.Markdown(MARKDOWN_OPENF1_EXAMPLES)
api_string = gr.Textbox(label="Full API string", placeholder="e.g. https://api.openf1.org/v1/sessions?driver_number=16")
btn = gr.Button("Send API request")
output = gr.JSON()
btn.click(openf1_tools.send_request, inputs=api_string, outputs=output)
return openf1_tools_tab
# OpenF1 tabs
named_interfaces = {
"About": markdown_tab,
"Driver Championship Standings": iface_driver_championship_standings,
"Constructor Championship Standings": iface_constructor_championship_standings,
"Event Info": iface_event_info,
"Season Calendar": iface_season_calendar,
"Track Visualizations": iface_track_visualization,
"Session Results": iface_session_results,
"Driver Info": iface_driver_info,
"Constructor Info": iface_constructor_info,
"OpenF1 Tools": openf1_tools_tab()
}
# Tab names and interfaces
tab_names = list(named_interfaces.keys())
interface_list = list(named_interfaces.values())
# Combine all the interfaces into a single TabbedInterface
gradio_server = gr.TabbedInterface(
interface_list,
tab_names=tab_names,
title="๐Ÿ Formula 1 MCP server ๐ŸŽ๏ธ"
)
# Launch the interface and MCP server
if __name__ == "__main__":
gradio_server.launch(mcp_server=True)