Skip to main content
Glama

MaverickMCP

by wshobson
MIT License
165
  • Apple
base.py5.14 kB
""" Base validation models and common validators for Maverick-MCP. This module provides base classes and common validation functions used across all validation models. """ import re from datetime import UTC, datetime from typing import Annotated from pydantic import BaseModel, ConfigDict, Field, field_validator from maverick_mcp.config.settings import get_settings settings = get_settings() # Custom type annotations TickerSymbol = Annotated[ str, Field( min_length=settings.validation.min_symbol_length, max_length=settings.validation.max_symbol_length, pattern=r"^[A-Z0-9\-\.]{1,10}$", description="Stock ticker symbol (e.g., AAPL, BRK.B, SPY)", ), ] DateString = Annotated[ str, Field(pattern=r"^\d{4}-\d{2}-\d{2}$", description="Date in YYYY-MM-DD format") ] PositiveInt = Annotated[int, Field(gt=0, description="Positive integer value")] PositiveFloat = Annotated[float, Field(gt=0.0, description="Positive float value")] Percentage = Annotated[ float, Field(ge=0.0, le=100.0, description="Percentage value (0-100)") ] class StrictBaseModel(BaseModel): """ Base model with strict validation settings. - Forbids extra fields - Validates on assignment - Uses strict mode for type coercion """ model_config = ConfigDict( extra="forbid", validate_assignment=True, strict=True, str_strip_whitespace=True, json_schema_extra={"examples": []}, ) class TickerValidator: """Common ticker validation methods.""" @staticmethod def validate_ticker(value: str) -> str: """Validate and normalize ticker symbol.""" # Convert to uppercase ticker = value.upper().strip() # Check pattern pattern = f"^[A-Z0-9\\-\\.]{{1,{settings.validation.max_symbol_length}}}$" if not re.match(pattern, ticker): raise ValueError( f"Invalid ticker symbol: {value}. " f"Must be {settings.validation.min_symbol_length}-{settings.validation.max_symbol_length} characters, alphanumeric with optional . or -" ) return ticker @staticmethod def validate_ticker_list(values: list[str]) -> list[str]: """Validate and normalize a list of tickers.""" if not values: raise ValueError("At least one ticker symbol is required") # Remove duplicates while preserving order seen = set() unique_tickers = [] for ticker in values: normalized = TickerValidator.validate_ticker(ticker) if normalized not in seen: seen.add(normalized) unique_tickers.append(normalized) return unique_tickers class DateValidator: """Common date validation methods.""" @staticmethod def validate_date_string(value: str) -> str: """Validate date string format.""" try: datetime.strptime(value, "%Y-%m-%d") except ValueError: raise ValueError(f"Invalid date format: {value}. Must be YYYY-MM-DD") return value @staticmethod def validate_date_range(start_date: str, end_date: str) -> tuple[str, str]: """Validate that end_date is after start_date.""" start = datetime.strptime(start_date, "%Y-%m-%d") end = datetime.strptime(end_date, "%Y-%m-%d") if end < start: raise ValueError( f"End date ({end_date}) must be after start date ({start_date})" ) # Check dates aren't too far in the future today = datetime.now(UTC).date() if end.date() > today: raise ValueError(f"End date ({end_date}) cannot be in the future") return start_date, end_date class PaginationMixin(BaseModel): """Mixin for pagination parameters.""" limit: PositiveInt = Field( default=20, le=100, description="Maximum number of results to return" ) offset: int = Field(default=0, ge=0, description="Number of results to skip") class DateRangeMixin(BaseModel): """Mixin for date range parameters.""" start_date: DateString | None = Field( default=None, description="Start date in YYYY-MM-DD format" ) end_date: DateString | None = Field( default=None, description="End date in YYYY-MM-DD format" ) @field_validator("end_date") @classmethod def validate_date_range(cls, v: str | None, info) -> str | None: """Ensure end_date is after start_date if both are provided.""" if v is None: return v start = info.data.get("start_date") if start is not None: DateValidator.validate_date_range(start, v) return v class BaseRequest(BaseModel): """Base class for all API request models.""" model_config = ConfigDict( str_strip_whitespace=True, validate_assignment=True, extra="forbid", ) class BaseResponse(BaseModel): """Base class for all API response models.""" model_config = ConfigDict( validate_assignment=True, use_enum_values=True, )

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/wshobson/maverick-mcp'

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