Skip to main content
Glama
YuchengMaUTK

Unofficial WCA MCP Server

by YuchengMaUTK
client.py11.4 kB
"""WCA API client implementation.""" import asyncio import logging from typing import Any, Dict, List, Optional from urllib.parse import urljoin, urlencode import httpx from pydantic import ValidationError from .config import get_settings from .models.base import APIError, PaginatedResponse logger = logging.getLogger(__name__) class WCAAPIClient: """Async HTTP client for the WCA API.""" def __init__(self, base_url: Optional[str] = None, timeout: float = 30.0): """Initialize the WCA API client. Args: base_url: Base URL for the WCA API. Defaults to settings. timeout: Request timeout in seconds. """ settings = get_settings() self.base_url = base_url or settings.api_base_url self.timeout = timeout self._client: Optional[httpx.AsyncClient] = None async def __aenter__(self): """Async context manager entry.""" await self._ensure_client() return self async def __aexit__(self, exc_type, exc_val, exc_tb): """Async context manager exit.""" if self._client: await self._client.aclose() self._client = None async def _ensure_client(self) -> None: """Ensure HTTP client is initialized.""" if not self._client: self._client = httpx.AsyncClient( timeout=httpx.Timeout(self.timeout), limits=httpx.Limits(max_keepalive_connections=10, max_connections=20), headers={ "User-Agent": "WCA-MCP-Server/1.0.0", "Accept": "application/json", } ) async def _make_request( self, endpoint: str, params: Optional[Dict[str, Any]] = None, retries: int = 3 ) -> Dict[str, Any]: """Make an HTTP request to the WCA API. Args: endpoint: API endpoint path params: Query parameters retries: Number of retry attempts Returns: JSON response data Raises: APIError: If the request fails after retries """ await self._ensure_client() # Ensure proper URL construction for static API if self.base_url.endswith('/'): url = self.base_url + endpoint else: url = self.base_url + '/' + endpoint if params: # Filter out None values params = {k: v for k, v in params.items() if v is not None} last_exception = None for attempt in range(retries + 1): try: logger.debug(f"Making request to {url} with params {params} (attempt {attempt + 1})") response = await self._client.get(url, params=params) response.raise_for_status() data = response.json() logger.debug(f"Request successful, received {len(str(data))} bytes") return data except httpx.HTTPStatusError as e: logger.warning(f"HTTP error {e.response.status_code} for {url}: {e}") if e.response.status_code == 404: raise APIError(f"Resource not found: {endpoint}", status_code=404) elif e.response.status_code >= 500 and attempt < retries: # Retry on server errors await asyncio.sleep(2 ** attempt) # Exponential backoff continue else: raise APIError(f"HTTP {e.response.status_code}: {e}", status_code=e.response.status_code) except (httpx.RequestError, httpx.TimeoutException) as e: logger.warning(f"Request error for {url}: {e}") last_exception = e if attempt < retries: await asyncio.sleep(2 ** attempt) # Exponential backoff continue except Exception as e: logger.error(f"Unexpected error for {url}: {e}") raise APIError(f"Unexpected error: {e}") # If we get here, all retries failed raise APIError(f"Request failed after {retries + 1} attempts: {last_exception}") async def get_competition(self, competition_id: str) -> Dict[str, Any]: """Get a specific competition by ID. Args: competition_id: Competition ID Returns: Competition data """ return await self._make_request(f"competitions/{competition_id}.json") async def get_competitions_by_date( self, year: int, month: int, day: int ) -> Dict[str, Any]: """Get competitions on a specific date. Args: year: Year (e.g., 2023, 2024) month: Month (1-12) day: Day (1-31) Returns: Competition data for the specific date """ month_str = f"{month:02d}" # Ensure 2-digit format day_str = f"{day:02d}" # Ensure 2-digit format endpoint = f"competitions/{year}/{month_str}/{day_str}.json" return await self._make_request(endpoint) async def get_competitions_by_event( self, event_id: str, page: int = 1 ) -> Dict[str, Any]: """Get competitions that feature a specific event. Args: event_id: Event ID (e.g., "333", "222", "333bf") page: Page number (1-based) Returns: Paginated competition data for the event """ if page == 1: endpoint = f"competitions/{event_id}.json" else: endpoint = f"competitions/{event_id}-page-{page}.json" return await self._make_request(endpoint) async def get_person(self, wca_id: str) -> Dict[str, Any]: """Get a specific person by WCA ID. Args: wca_id: WCA ID (e.g., "2003SEAR02") Returns: Person data """ return await self._make_request(f"persons/{wca_id}.json") async def get_rankings( self, event_id: str, region: str = "world", type: str = "single", page: int = 1, per_page: int = 25 ) -> Dict[str, Any]: """Get rankings for an event. Args: event_id: Event ID (e.g., "333") region: Region code ("world", continent code, or country ISO2) type: Ranking type ("single" or "average") page: Page number (1-based) per_page: Items per page (max 100) Returns: Paginated ranking data """ # The static API uses a specific URL format: rank/{region}/{type}/{event}.json return await self._make_request(f"rank/{region}/{type}/{event_id}.json") async def get_competition_results( self, competition_id: str ) -> Dict[str, Any]: """Get all results for a competition. Args: competition_id: Competition ID Returns: All result data for the competition """ return await self._make_request(f"results/{competition_id}.json") async def get_competition_event_results( self, competition_id: str, event_id: str ) -> Dict[str, Any]: """Get results for a specific event in a competition. Args: competition_id: Competition ID event_id: Event ID (e.g., "333", "222") Returns: Result data for the specific event in the competition """ return await self._make_request(f"results/{competition_id}/{event_id}.json") async def get_championships( self, page: int = 1, per_page: int = 25, **filters ) -> Dict[str, Any]: """Get championships with optional filtering. Args: page: Page number (1-based) - Note: static API doesn't support pagination per_page: Items per page (max 100) - Note: static API doesn't support pagination **filters: Additional filter parameters - Note: static API has limited filtering Returns: Paginated championship data """ # For the static API, we get all championships and handle filtering/pagination client-side data = await self._make_request("championships.json") # Handle filtering by type if specified items = data.get("items", []) if isinstance(data, dict) else [] if "type" in filters: filter_type = filters["type"].lower() if filter_type == "world": items = [item for item in items if item.get("region") == "world"] else: # Filter by region (continent or country) items = [item for item in items if item.get("region", "").lower() == filter_type] # Simple client-side pagination start_idx = (page - 1) * per_page end_idx = start_idx + per_page paginated_items = items[start_idx:end_idx] return { "items": paginated_items, "total": len(items), "page": page, "per_page": per_page, "pagination": { "page": page, "size": per_page, "total_pages": (len(items) + per_page - 1) // per_page } } async def get_championship(self, championship_id: str) -> Dict[str, Any]: """Get a specific championship by ID. Args: championship_id: Championship ID Returns: Championship data """ return await self._make_request(f"championships/{championship_id}.json") async def get_events(self) -> List[Dict[str, Any]]: """Get all WCA events. Returns: List of event data """ data = await self._make_request("events.json") # The API returns paginated data with items array if isinstance(data, dict) and "items" in data: return data["items"] # Fallback for other formats if isinstance(data, list): return data return data.get("events", []) async def get_countries(self) -> List[Dict[str, Any]]: """Get all countries. Returns: List of country data """ data = await self._make_request("countries.json") # The API returns paginated data with items array if isinstance(data, dict) and "items" in data: return data["items"] return data.get("countries", []) async def get_continents(self) -> List[Dict[str, Any]]: """Get all continents. Returns: List of continent data """ data = await self._make_request("continents.json") # The API returns paginated data with items array if isinstance(data, dict) and "items" in data: return data["items"] return data.get("continents", [])

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