Skip to main content
Glama
YuchengMaUTK

Unofficial WCA MCP Server

by YuchengMaUTK
main.py20.8 kB
"""Main MCP server implementation for the WCA MCP Server.""" import logging from typing import List, Dict, Any from fastmcp import FastMCP from .config import get_settings from .client import WCAAPIClient from .models.base import APIError def setup_logging(level: str = "INFO") -> None: """Set up logging configuration.""" logging.basicConfig( level=getattr(logging, level.upper()), format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) # Initialize settings and logging settings = get_settings() setup_logging(settings.log_level) # Create the FastMCP server instance mcp = FastMCP("WCA MCP Server") @mcp.tool() async def get_wca_events() -> List[Dict[str, Any]]: """Get all official WCA events. Returns a list of all official World Cube Association events including event IDs, names, and formats. Returns: List of WCA events with their details """ try: async with WCAAPIClient() as client: events = await client.get_events() return events except APIError as e: raise Exception(f"Failed to fetch WCA events: {e}") except Exception as e: raise Exception(f"Unexpected error fetching WCA events: {e}") @mcp.tool() async def get_wca_countries() -> List[Dict[str, Any]]: """Get all WCA countries. Returns a list of all countries recognized by the World Cube Association including country names and ISO2 codes used for regional filtering. Returns: List of WCA countries with names and ISO2 codes """ try: async with WCAAPIClient() as client: countries = await client.get_countries() return countries except APIError as e: raise Exception(f"Failed to fetch WCA countries: {e}") except Exception as e: raise Exception(f"Unexpected error fetching WCA countries: {e}") @mcp.tool() async def get_wca_continents() -> List[Dict[str, Any]]: """Get all WCA continents. Returns a list of all continents recognized by the World Cube Association including continent names and identifiers used for regional filtering. Returns: List of WCA continents with names and identifiers """ try: async with WCAAPIClient() as client: continents = await client.get_continents() return continents except APIError as e: raise Exception(f"Failed to fetch WCA continents: {e}") except Exception as e: raise Exception(f"Unexpected error fetching WCA continents: {e}") @mcp.tool() async def get_person_by_wca_id( wca_id: str, competition_id: str = None, include_competition_results: bool = False, include_personal_records: bool = True, include_rankings: bool = True, include_medals: bool = True, max_recent_competitions: int = 5 ) -> Dict[str, Any]: """Get detailed information about a specific WCA competitor by their WCA ID. Returns information about a speedcuber with configurable detail levels. By default, returns basic info, personal records, rankings, and medals without the verbose competition results that can make responses extremely long. Args: wca_id: WCA ID of the person (e.g., "2003SEAR02") competition_id: Optional competition ID to get results from a specific competition (e.g., "WC2023") include_competition_results: Include detailed competition results (default: False) include_personal_records: Include personal best records (default: True) include_rankings: Include current world/continental/national rankings (default: True) include_medals: Include medal counts (default: True) max_recent_competitions: If including results, limit to N most recent competitions (default: 5) Returns: Filtered person information based on the specified parameters """ try: async with WCAAPIClient() as client: person_data = await client.get_person(wca_id) # Create filtered response filtered_data = { "id": person_data.get("id"), "name": person_data.get("name"), "slug": person_data.get("slug"), "country": person_data.get("country"), "numberOfCompetitions": person_data.get("numberOfCompetitions"), "numberOfChampionships": person_data.get("numberOfChampionships"), } # Add competition IDs list (always include as it's lightweight) if "competitionIds" in person_data: filtered_data["competitionIds"] = person_data["competitionIds"] if "championshipIds" in person_data: filtered_data["championshipIds"] = person_data["championshipIds"] # Add personal records if requested if include_personal_records and "records" in person_data: filtered_data["records"] = person_data["records"] # Add rankings if requested if include_rankings and "rank" in person_data: filtered_data["rank"] = person_data["rank"] # Add medals if requested if include_medals and "medals" in person_data: filtered_data["medals"] = person_data["medals"] # Handle specific competition results if competition_id is provided if competition_id: competition_ids = person_data.get("competitionIds", []) results = person_data.get("results", {}) if competition_id in competition_ids: # Person participated in this competition if competition_id in results: # Results data is available filtered_data["results"] = {competition_id: results[competition_id]} filtered_data["_results_note"] = f"Showing results from competition: {competition_id}" else: # Person participated but no results data available filtered_data["results"] = {} filtered_data["_results_note"] = f"Person participated in {competition_id} but no results data available" else: # Person did not participate in this competition filtered_data["results"] = {} filtered_data["_results_note"] = f"Person did not participate in competition: {competition_id}" # Add competition results if requested (with optional limiting) elif include_competition_results and "results" in person_data: results = person_data["results"] if max_recent_competitions and max_recent_competitions > 0: # Get the most recent competitions (competition IDs are typically in chronological order) competition_ids = person_data.get("competitionIds", []) recent_competition_ids = competition_ids[-max_recent_competitions:] if competition_ids else [] # Filter results to only include recent competitions filtered_results = { comp_id: results[comp_id] for comp_id in recent_competition_ids if comp_id in results } filtered_data["results"] = filtered_results filtered_data["_results_note"] = f"Showing results from {len(filtered_results)} most recent competitions out of {len(results)} total" else: filtered_data["results"] = results elif not include_competition_results and not competition_id: # Explicitly exclude results when not requested filtered_data["_results_note"] = f"Competition results excluded (set include_competition_results=True to include). Total competitions: {person_data.get('numberOfCompetitions', 0)}" return filtered_data except APIError as e: raise Exception(f"Failed to get person {wca_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting person {wca_id}: {e}") @mcp.tool() async def get_rankings( event_id: str, region: str = "world", ranking_type: str = "single", page: int = 1, per_page: int = 25 ) -> Dict[str, Any]: """Get current world rankings and records for a specific event. Returns the current rankings for any WCA event, which includes world records and top performers. Can filter by region (world, continent, or country). Args: event_id: WCA event ID (e.g., "333" for 3x3x3 Cube, "222" for 2x2x2) region: Region code - "world" for world rankings, continent codes like "Europe", or country ISO2 codes like "US", "CN" (default: "world") ranking_type: Type of ranking - "single" for single solve records or "average" for average records (default: "single") page: Page number (1-based, default: 1) per_page: Items per page (max 100, default: 25) Returns: Paginated ranking data with current records and top performers """ try: async with WCAAPIClient() as client: rankings_data = await client.get_rankings( event_id=event_id, region=region, type=ranking_type, page=page, per_page=per_page ) return rankings_data except APIError as e: raise Exception(f"Failed to get rankings for {event_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting rankings for {event_id}: {e}") @mcp.tool() async def search_competitions_by_date( year: int, month: int, day: int ) -> Dict[str, Any]: """Search for WCA competitions on a specific date. Returns a small, focused list of competitions that occurred on the exact date specified. This typically returns 1-5 competitions, making it perfect for LLM processing. Args: year: Year (e.g., 2023, 2024) month: Month (1-12) day: Day (1-31) Returns: Competition data for the specific date (typically 1-5 competitions) Example: search_competitions_by_date(year=2024, month=3, day=15) - Competitions on March 15, 2024 """ try: async with WCAAPIClient() as client: competitions_data = await client.get_competitions_by_date(year, month, day) return competitions_data except APIError as e: raise Exception(f"Failed to search competitions by date: {e}") except Exception as e: raise Exception(f"Unexpected error searching competitions by date: {e}") @mcp.tool() async def search_competitions_by_event( event_id: str, page: int = 1 ) -> Dict[str, Any]: """Search for WCA competitions that include a specific event. Returns competitions that feature the specified WCA event. Results are paginated to manage the response size. Args: event_id: WCA event ID (e.g., "333" for 3x3x3 Cube, "222" for 2x2x2, "333bf" for 3x3x3 Blindfolded) page: Page number for pagination (default: 1) Returns: Paginated competition data for competitions featuring the specified event Example: search_competitions_by_event(event_id="333bf") - Competitions with 3x3x3 Blindfolded """ try: async with WCAAPIClient() as client: competitions_data = await client.get_competitions_by_event(event_id, page) return competitions_data except APIError as e: raise Exception(f"Failed to search competitions by event: {e}") except Exception as e: raise Exception(f"Unexpected error searching competitions by event: {e}") @mcp.tool() async def get_competition_by_id(competition_id: str) -> Dict[str, Any]: """Get detailed information about a specific competition by its ID. Returns comprehensive information about a WCA competition including venue details, events, results, and organizer information. Args: competition_id: WCA competition ID (e.g., "WC2023") Returns: Detailed competition information """ try: async with WCAAPIClient() as client: competition_data = await client.get_competition(competition_id) return competition_data except APIError as e: raise Exception(f"Failed to get competition {competition_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting competition {competition_id}: {e}") @mcp.tool() async def search_championships( page: int = 1, championship_type: str = None ) -> Dict[str, Any]: """Search for WCA championships with optional filtering. Returns championship competitions which are the most prestigious competitions in speedcubing including World Championships, Continental Championships, and National Championships. Args: page: Page number for pagination (default: 1) championship_type: Filter by championship type: - "world" for World Championships - continent codes like "Europe", "Asia", "North America" for Continental Championships - country ISO2 codes like "US", "CN", "DE" for National Championships - None for all championships (default) Returns: Paginated championship data with competition details Example: search_championships(championship_type="world") - World Championships only search_championships(championship_type="US") - US National Championships """ try: async with WCAAPIClient() as client: # Build filters based on championship_type filters = {} if championship_type: # The API expects the type parameter for filtering filters["type"] = championship_type.lower() championships_data = await client.get_championships( page=page, **filters ) return championships_data except APIError as e: raise Exception(f"Failed to search championships: {e}") except Exception as e: raise Exception(f"Unexpected error searching championships: {e}") @mcp.tool() async def get_championship_details(championship_id: str) -> Dict[str, Any]: """Get detailed information about a specific championship by its ID. Returns comprehensive information about a WCA championship including venue details, events, results, competitors, and championship-specific information like regional significance. Args: championship_id: Championship ID (e.g., "WC2023" for World Championship 2023, "Euro2022" for European Championship 2022) Returns: Detailed championship information including all competition data plus championship-specific details Example: get_championship_details("WC2023") - World Championship 2023 details """ try: async with WCAAPIClient() as client: championship_data = await client.get_championship(championship_id) return championship_data except APIError as e: raise Exception(f"Failed to get championship {championship_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting championship {championship_id}: {e}") @mcp.tool() async def get_competition_results(competition_id: str) -> Dict[str, Any]: """Get all results for a WCA competition. Returns comprehensive results data for all events in a competition, including competitor performances, solve breakdowns, round information, and final rankings. This provides complete competition outcome data. Args: competition_id: WCA competition ID (e.g., "WC2023", "CubingUSANationals2024") Returns: Complete results data for all events in the competition including: - Individual solve times and averages - Round-by-round progression - Final rankings and positions - DNF/DNS information - Competitor details Example: get_competition_results("WC2023") - All results from World Championship 2023 """ try: async with WCAAPIClient() as client: results_data = await client.get_competition_results(competition_id) return results_data except APIError as e: raise Exception(f"Failed to get results for competition {competition_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting results for competition {competition_id}: {e}") @mcp.tool() async def get_competition_event_results( competition_id: str, event_id: str, round: str = "Final", limit: int = None, include_solves: bool = False ) -> Dict[str, Any]: """Get results for a specific event within a WCA competition. Returns focused results data for a single event in a competition. By default, returns only Final round results for better LLM processing. Args: competition_id: WCA competition ID (e.g., "WC2025", "CubingUSANationals2024") event_id: WCA event ID (e.g., "333" for 3x3x3 Cube, "222" for 2x2x2, "333bf" for 3x3x3 Blindfolded, "444" for 4x4x4) round: Specific round to filter by (default: "Final"). Use "all" for all rounds, or specify: "Final", "Semi Final", "Second round", "First round" limit: Maximum number of results to return (optional) include_solves: Whether to include detailed solve times (default: False) Returns: Filtered results data including: - Competitor performances for the specified round - Position, best single, and average times - Optionally detailed solve breakdowns Example: get_competition_event_results("WC2025", "333") - Final round only (16 results) get_competition_event_results("WC2025", "333", round="all") - All rounds (2490 results) get_competition_event_results("WC2025", "333", limit=10) - Top 10 from Final """ try: async with WCAAPIClient() as client: # Get all results first all_results = await client.get_competition_event_results(competition_id, event_id) if not all_results or 'items' not in all_results: return all_results items = all_results['items'] # Filter by round if specified if round != "all": items = [item for item in items if item.get('round') == round] # Apply limit if specified if limit: items = items[:limit] # Clean up data for LLM consumption cleaned_items = [] for item in items: cleaned_item = { 'personId': item.get('personId'), 'round': item.get('round'), 'position': item.get('position'), 'best': item.get('best'), 'average': item.get('average') } # Include solves only if requested if include_solves: cleaned_item['solves'] = item.get('solves', []) cleaned_items.append(cleaned_item) # Return filtered and cleaned results return { 'pagination': { 'page': 1, 'size': len(cleaned_items) }, 'total': len(cleaned_items), 'filter': { 'round': round, 'limit': limit, 'include_solves': include_solves }, 'items': cleaned_items } except APIError as e: raise Exception(f"Failed to get {event_id} results for competition {competition_id}: {e}") except Exception as e: raise Exception(f"Unexpected error getting {event_id} results for competition {competition_id}: {e}") # This is the main server instance that MCP clients will connect to

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YuchengMaUTK/unofficial-wca-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server