Skip to main content
Glama

Fatebook MCP Server

models.py6.7 kB
from pydantic import BaseModel, Field, field_validator from typing import Optional, List, Literal, Union from datetime import datetime class User(BaseModel): """User information for forecasts and comments""" id: Optional[Union[str, int]] = None name: str @field_validator("id") @classmethod def convert_id_to_string(cls, v): """Convert integer IDs to strings""" return str(v) if v is not None else v class Tag(BaseModel): """Tag model for question categorization""" id: Optional[Union[str, int]] = None name: str @field_validator("id") @classmethod def convert_id_to_string(cls, v): """Convert integer IDs to strings""" return str(v) if v is not None else v class Forecast(BaseModel): """Forecast made on a question""" id: Optional[Union[str, int]] = None forecast: float = Field(ge=0, le=1, description="Forecast value between 0 and 1") user: User created_at: Optional[datetime] = Field(None, alias="createdAt") option_id: Optional[Union[str, int]] = Field( None, alias="optionId", description="For multi-choice questions" ) @field_validator("id", "option_id") @classmethod def convert_id_to_string(cls, v): """Convert integer IDs to strings""" return str(v) if v is not None else v class Config: populate_by_name = True by_alias = True # Use aliases when serializing class Comment(BaseModel): """Comment on a question""" id: Optional[Union[str, int]] = None comment: str user: User created_at: Optional[datetime] = Field(None, alias="createdAt") @field_validator("id") @classmethod def convert_id_to_string(cls, v): """Convert integer IDs to strings""" return str(v) if v is not None else v class Config: populate_by_name = True by_alias = True # Use aliases when serializing class Question(BaseModel): """Fatebook question model with optional fields for detailed responses""" # Core fields (id is optional since getQuestion doesn't return it) id: Optional[str] = None title: str type: Literal["BINARY", "NUMERIC", "MULTIPLE_CHOICE"] = "BINARY" resolved: bool = False # Timestamps created_at: datetime = Field(alias="createdAt") resolve_by: datetime = Field(alias="resolveBy") resolved_at: Optional[datetime] = Field(None, alias="resolvedAt") # Resolution information resolution: Optional[Literal["YES", "NO", "AMBIGUOUS"]] = None # Additional content (typically in detailed view) notes: Optional[str] = None # Related data (typically in detailed view) forecasts: Optional[List[Forecast]] = Field( default=None, description="List of forecasts on this question" ) tags: Optional[List[Tag]] = Field(default=None, description="Tags associated with the question") comments: Optional[List[Comment]] = Field(default=None, description="Comments on the question") # Visibility settings (typically in detailed view) shared_publicly: Optional[bool] = Field(None, alias="sharedPublicly") unlisted: Optional[bool] = None hide_forecasts_until: Optional[datetime] = Field(None, alias="hideForecastsUntil") share_with_lists: Optional[List[str]] = Field(None, alias="shareWithLists") share_with_email: Optional[List[str]] = Field(None, alias="shareWithEmail") # Additional fields from getQuestion endpoint your_latest_prediction: Optional[str] = Field(None, alias="yourLatestPrediction") question_scores: Optional[List] = Field(None, alias="questionScores") class Config: populate_by_name = True by_alias = True # Use aliases when serializing # Computed properties @property def forecast_count(self) -> int: """Number of forecasts on this question""" return len(self.forecasts) if self.forecasts else 0 @property def status_text(self) -> str: """Human-readable status string""" if self.resolved: return f"✅ RESOLVED ({self.resolution})" return "⏳ OPEN" def format_short(self) -> str: """Format for list display""" tags_text = ", ".join([tag.name for tag in self.tags]) if self.tags else "" tags_display = f" | Tags: {tags_text}" if tags_text else "" forecast_text = ( f" | {self.forecast_count} forecast{'s' if self.forecast_count != 1 else ''}" ) id_text = f" | ID: {self.id}" if self.id else "" return f"**{self.title}**\n{self.status_text}{id_text}{forecast_text}{tags_display}" def format_detailed(self) -> str: """Format for detailed single question display""" lines = [ f"**{self.title}**", ] if self.id: lines.append(f"ID: {self.id}") lines.extend( [ f"Type: {self.type}", f"Created: {self.created_at.isoformat()}", f"Resolve By: {self.resolve_by.isoformat()}", ] ) status = "✅ Resolved" if self.resolved else "⏳ Open" if self.resolved and self.resolved_at: status += f" as {self.resolution} on {self.resolved_at.isoformat()}" lines.append(f"Status: {status}") if self.notes: lines.append(f"Notes: {self.notes}") if self.forecasts: lines.append(f"Forecasts ({len(self.forecasts)}):") for forecast in self.forecasts: lines.append(f" • {forecast.user.name}: {forecast.forecast:.0%}") if self.tags: tag_names = [tag.name for tag in self.tags] lines.append(f"Tags: {', '.join(tag_names)}") if self.comments: lines.append(f"Comments ({len(self.comments)}):") for comment in self.comments: lines.append(f" • {comment.user.name}: {comment.comment}") visibility = [] if self.shared_publicly: visibility.append("Public") if self.unlisted: visibility.append("Unlisted") if visibility: lines.append(f"Visibility: {', '.join(visibility)}") return "\n".join(lines) class QuestionsResponse(BaseModel): """Response from getQuestions endpoint""" items: List[Question] cursor: Optional[str] = None class QuestionsList(BaseModel): """List of questions for MCP responses - matches expected MCP schema""" result: List[Question] class Config: populate_by_name = True by_alias = True class QuestionReference(BaseModel): """Minimal question reference with id and title""" id: str title: str

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/an1lam/fatebook-mcp'

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