Skip to content

Data Models

Authoritative, auto-generated reference for the Pydantic models that define tool inputs and outputs. These are generated directly from gopher_mcp.models, so they never drift from the code. For usage examples and error-handling recipes, see the API Reference.

Request Models

gopher_mcp.models.GopherFetchRequest

Bases: BaseModel

Request model for gopher.fetch tool.

url class-attribute instance-attribute

url: str = Field(..., description='Gopher URL to fetch (e.g., gopher://gopher.floodgap.com/1/)', examples=['gopher://gopher.floodgap.com/1/', 'gopher://gopher.floodgap.com/0/about.txt'])

validate_gopher_url classmethod

validate_gopher_url(v: str) -> str

Validate that the URL is a proper Gopher URL.

Source code in src/gopher_mcp/models.py
@field_validator("url")
@classmethod
def validate_gopher_url(cls, v: str) -> str:
    """Validate that the URL is a proper Gopher URL."""
    if not v.startswith("gopher://"):
        raise ValueError("URL must start with 'gopher://'")
    if len(v.encode("utf-8")) > 8192:
        raise ValueError("URL must not exceed 8192 bytes")
    return v

gopher_mcp.models.GeminiFetchRequest

Bases: BaseModel

Request model for gemini_fetch tool.

url class-attribute instance-attribute

url: str = Field(..., description='Gemini URL to fetch (e.g., gemini://gemini.circumlunar.space/)', examples=['gemini://gemini.circumlunar.space/', 'gemini://gemini.circumlunar.space/docs/specification.gmi'])

validate_gemini_url classmethod

validate_gemini_url(v: str) -> str

Validate that the URL is a proper Gemini URL.

Source code in src/gopher_mcp/models.py
@field_validator("url")
@classmethod
def validate_gemini_url(cls, v: str) -> str:
    """Validate that the URL is a proper Gemini URL."""
    if not v.startswith("gemini://"):
        raise ValueError("URL must start with 'gemini://'")
    if len(v.encode("utf-8")) > 1024:
        raise ValueError("URL must not exceed 1024 bytes")
    return v

Gopher Result Models

gopher_mcp.models.GopherMenuItem

Bases: BaseModel

Model for a single Gopher menu item.

type class-attribute instance-attribute

type: str = Field(..., description='Gopher item type (single character)')

title class-attribute instance-attribute

title: str = Field(..., description='Human-readable item title')

selector class-attribute instance-attribute

selector: str = Field(..., description='Selector string for this item')

host class-attribute instance-attribute

host: str = Field(..., description='Hostname where item resides')

port class-attribute instance-attribute

port: int = Field(..., ge=0, le=65535, description='Port number (typically 70)')

next_url class-attribute instance-attribute

next_url: str = Field(..., alias='nextUrl', description='Fully formed gopher:// URL for this item')

gopher_mcp.models.MenuResult

Bases: BaseModel

Result model for Gopher menu responses.

kind class-attribute instance-attribute

kind: Literal['menu'] = 'menu'

items class-attribute instance-attribute

items: list[GopherMenuItem] = Field(..., description='List of menu items')

truncated class-attribute instance-attribute

truncated: bool = Field(default=False, description='True if the menu had more items than the render limit and `items` was truncated')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.TextResult

Bases: BaseModel

Result model for Gopher text responses.

kind class-attribute instance-attribute

kind: Literal['text'] = 'text'

charset class-attribute instance-attribute

charset: str = Field(default='utf-8', description='Character encoding')

bytes class-attribute instance-attribute

bytes: int = Field(..., ge=0, description='Size of content in bytes')

text class-attribute instance-attribute

text: str = Field(..., description='Text content')

truncated class-attribute instance-attribute

truncated: bool = Field(default=False, description='True if `text` was truncated to the render limit (`bytes` still reports the full original size)')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.BinaryResult

Bases: BaseModel

Result model for Gopher binary responses.

kind class-attribute instance-attribute

kind: Literal['binary'] = 'binary'

bytes class-attribute instance-attribute

bytes: int = Field(..., ge=0, description='Size of content in bytes')

mime_type class-attribute instance-attribute

mime_type: str | None = Field(None, alias='mimeType', description='Guessed MIME type')

note class-attribute instance-attribute

note: str = Field(default='Binary content not returned to preserve context', description='Note about binary handling')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.ErrorResult

Bases: BaseModel

Result model for error responses.

kind class-attribute instance-attribute

kind: Literal['error'] = 'error'

error class-attribute instance-attribute

error: dict[str, str] = Field(..., description='Error information')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

Gemini Result Models

gopher_mcp.models.GeminiSuccessResult

Bases: BaseModel

Result model for a successful Gemini response carrying TEXT content.

Binary success responses use :class:GeminiBinaryResult (metadata only), so content is always decoded text here.

kind class-attribute instance-attribute

kind: Literal['success'] = 'success'

mime_type class-attribute instance-attribute

mime_type: GeminiMimeType = Field(..., alias='mimeType', description='Content MIME type')

content class-attribute instance-attribute

content: str = Field(..., description='Decoded text response content')

size class-attribute instance-attribute

size: int = Field(..., ge=0, description='Content size in bytes')

truncated class-attribute instance-attribute

truncated: bool = Field(default=False, description='True if `content` was truncated to the render limit (`size` still reports the full original size).')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiBinaryResult

Bases: BaseModel

Result model for a successful BINARY Gemini response (metadata only).

Mirrors the Gopher :class:BinaryResult: the raw bytes are NOT returned to the model. A 1 MB body is ~1.4M base64 characters (~350k tokens), so inlining it would flood the context for content the model can't render anyway. The consumer gets the size and detected MIME type and can fetch the resource directly if it genuinely needs the bytes.

kind class-attribute instance-attribute

kind: Literal['binary'] = 'binary'

mime_type class-attribute instance-attribute

mime_type: GeminiMimeType = Field(..., alias='mimeType', description='Detected content MIME type')

size class-attribute instance-attribute

size: int = Field(..., ge=0, description='Content size in bytes')

note class-attribute instance-attribute

note: str = Field(default='Binary content not returned to preserve context', description='Note about binary handling')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiGemtextResult

Bases: BaseModel

Result model for gemtext content responses.

kind class-attribute instance-attribute

kind: Literal['gemtext'] = 'gemtext'

document class-attribute instance-attribute

document: GemtextDocument = Field(..., description='Parsed gemtext document')

raw_content class-attribute instance-attribute

raw_content: str = Field(..., alias='rawContent', description='Raw gemtext content')

charset class-attribute instance-attribute

charset: str = Field(default='utf-8', description='Character encoding')

lang class-attribute instance-attribute

lang: str | None = Field(None, description='Language tag')

size class-attribute instance-attribute

size: int = Field(..., description='Content size in bytes')

truncated class-attribute instance-attribute

truncated: bool = Field(default=False, description='True if the gemtext was truncated to the render limit (`size` still reports the full original byte size)')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiInputResult

Bases: BaseModel

Result model for input request responses (status 10/11).

kind class-attribute instance-attribute

kind: Literal['input'] = 'input'

prompt class-attribute instance-attribute

prompt: str = Field(..., description='Input prompt text')

sensitive class-attribute instance-attribute

sensitive: bool = Field(default=False, description='Whether input is sensitive')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiRedirectResult

Bases: BaseModel

Result model for redirect responses (status 30/31).

kind class-attribute instance-attribute

kind: Literal['redirect'] = 'redirect'

new_url class-attribute instance-attribute

new_url: str = Field(..., alias='newUrl', description='Redirect target URL')

permanent class-attribute instance-attribute

permanent: bool = Field(default=False, description='Whether redirect is permanent')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiErrorResult

Bases: BaseModel

Result model for error responses.

kind class-attribute instance-attribute

kind: Literal['error'] = 'error'

error class-attribute instance-attribute

error: dict[str, Any] = Field(..., description='Error information')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

gopher_mcp.models.GeminiCertificateResult

Bases: BaseModel

Result model for certificate request responses (status 60-62).

kind class-attribute instance-attribute

kind: Literal['certificate'] = 'certificate'

message class-attribute instance-attribute

message: str = Field(..., description='Certificate-related message')

status class-attribute instance-attribute

status: int = Field(default=60, ge=60, le=69, description='Gemini certificate status code: 60 required, 61 not authorized, 62 not valid')

required class-attribute instance-attribute

required: bool = Field(default=True, description='Whether the server is prompting for a certificate (status 60). False for 61/62, which are rejections of a presented identity.')

request_info class-attribute instance-attribute

request_info: dict[str, Any] = Field(default_factory=dict, alias='requestInfo', description='Information about the original request')

Gemtext Document Models

gopher_mcp.models.GemtextDocument

Bases: BaseModel

Model for parsed gemtext document.

lines class-attribute instance-attribute

lines: list[GemtextLine] = Field(..., description='Document lines')
links: list[GemtextLink] = Field(default_factory=list, description='Extracted links')
link_count: int

Get number of links in document.

has_headings property

has_headings: bool

Check if document has any headings.

line_count property

line_count: int

Get total number of lines in document.

content_summary property

content_summary: dict[str, int]

Get summary of content types for LLM consumption.

heading_hierarchy property

heading_hierarchy: list[dict[str, Any]]

Get document heading structure for navigation.

text_content property

text_content: str

Get plain text content (excluding markup) for search/analysis.

gopher_mcp.models.GemtextLine

Bases: BaseModel

Model for a single line in gemtext format.

type class-attribute instance-attribute

type: GemtextLineType = Field(..., description='Type of gemtext line')

content class-attribute instance-attribute

content: str = Field(..., description='Line content')
link: GemtextLink | None = Field(None, description='Link data (for link lines)')

level class-attribute instance-attribute

level: int | None = Field(None, description='Heading level (1-3, for headings)')

alt_text class-attribute instance-attribute

alt_text: str | None = Field(None, description='Alt text (for preformat blocks)')

heading class-attribute instance-attribute

heading: GemtextHeading | None = Field(None, description='Heading data (for heading lines)')

list_item class-attribute instance-attribute

list_item: GemtextList | None = Field(None, description='List data (for list lines)')

quote class-attribute instance-attribute

quote: GemtextQuote | None = Field(None, description='Quote data (for quote lines)')

preformat class-attribute instance-attribute

preformat: GemtextPreformat | None = Field(None, description='Preformat data (for preformat lines)')

Bases: BaseModel

Model for gemtext link lines.

url class-attribute instance-attribute

url: str = Field(..., description='Link URL (absolute or relative)')

text class-attribute instance-attribute

text: str | None = Field(None, description='Link text (optional)')

is_external property

is_external: bool

Whether the link points outside the current capsule.

External links carry a URL scheme (gemini://, https://, mailto: ...) or are protocol-relative (//host/...). Scheme-less relative links (foo.gmi, /abs, ./page) are internal.

validate_url_not_empty classmethod

validate_url_not_empty(v: str) -> str

Validate URL is not empty.

Source code in src/gopher_mcp/models.py
@field_validator("url")
@classmethod
def validate_url_not_empty(cls, v: str) -> str:
    """Validate URL is not empty."""
    if not v.strip():
        raise ValueError("Link URL cannot be empty")
    return v.strip()

gopher_mcp.models.GemtextHeading

Bases: BaseModel

Model for gemtext heading lines.

level class-attribute instance-attribute

level: int = Field(..., description='Heading level (1-3)', ge=1, le=3)

text class-attribute instance-attribute

text: str = Field(..., description='Heading text content')

raw_content class-attribute instance-attribute

raw_content: str = Field(..., description='Raw line content including # markers')

gopher_mcp.models.GemtextList

Bases: BaseModel

Model for gemtext list items.

text class-attribute instance-attribute

text: str = Field(..., description='List item text content')

raw_content class-attribute instance-attribute

raw_content: str = Field(..., description='Raw line content including * marker')

gopher_mcp.models.GemtextQuote

Bases: BaseModel

Model for gemtext quote lines.

text class-attribute instance-attribute

text: str = Field(..., description='Quote text content')

raw_content class-attribute instance-attribute

raw_content: str = Field(..., description='Raw line content including > marker')

gopher_mcp.models.GemtextPreformat

Bases: BaseModel

Model for gemtext preformat content.

content class-attribute instance-attribute

content: str = Field(..., description='Preformat content')

alt_text class-attribute instance-attribute

alt_text: str | None = Field(None, description='Alt text for accessibility')

is_toggle class-attribute instance-attribute

is_toggle: bool = Field(default=False, description='Whether this is a toggle line (```)')

language class-attribute instance-attribute

language: str | None = Field(None, description='Detected programming language')

metadata class-attribute instance-attribute

metadata: dict[str, Any] = Field(default_factory=dict, description='Additional metadata')

gopher_mcp.models.GemtextLineType

Bases: str, Enum

Types of lines in gemtext format.

TEXT class-attribute instance-attribute

TEXT = 'text'
LINK = 'link'

HEADING_1 class-attribute instance-attribute

HEADING_1 = 'heading1'

HEADING_2 class-attribute instance-attribute

HEADING_2 = 'heading2'

HEADING_3 class-attribute instance-attribute

HEADING_3 = 'heading3'

LIST_ITEM class-attribute instance-attribute

LIST_ITEM = 'list'

QUOTE class-attribute instance-attribute

QUOTE = 'quote'

PREFORMAT class-attribute instance-attribute

PREFORMAT = 'preformat'

MIME and Protocol Types

gopher_mcp.models.GeminiMimeType

Bases: BaseModel

Model for Gemini MIME type parsing.

type class-attribute instance-attribute

type: str = Field(..., description="Main MIME type (e.g., 'text')")

subtype class-attribute instance-attribute

subtype: str = Field(..., description="MIME subtype (e.g., 'gemini')")

charset class-attribute instance-attribute

charset: str = Field(default='utf-8', description='Character encoding')

lang class-attribute instance-attribute

lang: str | None = Field(None, description='Language tag (BCP47)')

full_type property

full_type: str

Get full MIME type string.

is_text property

is_text: bool

Check if this is a text MIME type.

is_gemtext property

is_gemtext: bool

Check if this is text/gemini.

is_binary property

is_binary: bool

Check if this is a binary MIME type.

is_image property

is_image: bool

Check if this is an image MIME type.

is_audio property

is_audio: bool

Check if this is an audio MIME type.

is_video property

is_video: bool

Check if this is a video MIME type.

is_application property

is_application: bool

Check if this is an application MIME type.

supports_charset

supports_charset() -> bool

Check if this MIME type supports charset parameter.

Source code in src/gopher_mcp/models.py
def supports_charset(self) -> bool:
    """Check if this MIME type supports charset parameter."""
    return self.is_text

get_file_extension

get_file_extension() -> str

Get common file extension for this MIME type.

Source code in src/gopher_mcp/models.py
def get_file_extension(self) -> str:
    """Get common file extension for this MIME type."""
    # Common MIME type to extension mappings
    extensions = {
        "text/gemini": ".gmi",
        "text/plain": ".txt",
        "text/html": ".html",
        "text/css": ".css",
        "text/javascript": ".js",
        "image/jpeg": ".jpg",
        "image/png": ".png",
        "image/gif": ".gif",
        "image/webp": ".webp",
        "image/bmp": ".bmp",
        "audio/mpeg": ".mp3",
        "audio/ogg": ".ogg",
        "audio/wav": ".wav",
        "video/mp4": ".mp4",
        "video/webm": ".webm",
        "application/pdf": ".pdf",
        "application/zip": ".zip",
        "application/json": ".json",
        "application/xml": ".xml",
    }
    return extensions.get(self.full_type, "")

gopher_mcp.models.GeminiStatusCode

Bases: IntEnum

Gemini protocol status codes.

INPUT class-attribute instance-attribute

INPUT = 10

SENSITIVE_INPUT class-attribute instance-attribute

SENSITIVE_INPUT = 11

SUCCESS class-attribute instance-attribute

SUCCESS = 20

TEMPORARY_REDIRECT class-attribute instance-attribute

TEMPORARY_REDIRECT = 30

PERMANENT_REDIRECT class-attribute instance-attribute

PERMANENT_REDIRECT = 31

TEMPORARY_FAILURE class-attribute instance-attribute

TEMPORARY_FAILURE = 40

SERVER_UNAVAILABLE class-attribute instance-attribute

SERVER_UNAVAILABLE = 41

CGI_ERROR class-attribute instance-attribute

CGI_ERROR = 42

PROXY_ERROR class-attribute instance-attribute

PROXY_ERROR = 43

SLOW_DOWN class-attribute instance-attribute

SLOW_DOWN = 44

PERMANENT_FAILURE class-attribute instance-attribute

PERMANENT_FAILURE = 50

NOT_FOUND class-attribute instance-attribute

NOT_FOUND = 51

GONE class-attribute instance-attribute

GONE = 52

PROXY_REQUEST_REFUSED class-attribute instance-attribute

PROXY_REQUEST_REFUSED = 53

BAD_REQUEST class-attribute instance-attribute

BAD_REQUEST = 59

CERTIFICATE_REQUIRED class-attribute instance-attribute

CERTIFICATE_REQUIRED = 60

CERTIFICATE_NOT_AUTHORIZED class-attribute instance-attribute

CERTIFICATE_NOT_AUTHORIZED = 61

CERTIFICATE_NOT_VALID class-attribute instance-attribute

CERTIFICATE_NOT_VALID = 62

gopher_mcp.models.GeminiResponse

Bases: BaseModel

Base model for Gemini protocol responses.

status class-attribute instance-attribute

status: GeminiStatusCode | int = Field(..., description='Gemini status code')

meta class-attribute instance-attribute

meta: str = Field(..., description='Status-dependent metadata')

body class-attribute instance-attribute

body: bytes | None = Field(None, description='Response body (if any)')

validate_meta_length classmethod

validate_meta_length(v: str) -> str

Validate meta field length (reasonable limit).

Source code in src/gopher_mcp/models.py
@field_validator("meta")
@classmethod
def validate_meta_length(cls, v: str) -> str:
    """Validate meta field length (reasonable limit)."""
    if len(v.encode("utf-8")) > 1024:
        raise ValueError("Meta field too long")
    return v

URL Models

gopher_mcp.models.GopherURL

Bases: BaseModel

Model for parsed Gopher URLs.

host class-attribute instance-attribute

host: str = Field(..., description='Hostname')

port class-attribute instance-attribute

port: int = Field(default=70, description='Port number')

gopher_type class-attribute instance-attribute

gopher_type: str = Field(default='1', alias='gopherType', description='Gopher item type')

selector class-attribute instance-attribute

selector: str = Field(default='', description='Selector string')

search class-attribute instance-attribute

search: str | None = Field(None, description='Search string for type 7 items')

validate_port classmethod

validate_port(v: int) -> int

Validate port number range.

Source code in src/gopher_mcp/models.py
@field_validator("port")
@classmethod
def validate_port(cls, v: int) -> int:
    """Validate port number range."""
    if not 1 <= v <= 65535:
        raise ValueError("Port must be between 1 and 65535")
    return v

validate_gopher_type classmethod

validate_gopher_type(v: str) -> str

Validate Gopher type is a single character.

Source code in src/gopher_mcp/models.py
@field_validator("gopher_type")
@classmethod
def validate_gopher_type(cls, v: str) -> str:
    """Validate Gopher type is a single character."""
    if len(v) != 1:
        raise ValueError("Gopher type must be a single character")
    return v

validate_host classmethod

validate_host(v: str) -> str

Validate hostname is not empty (mirrors GeminiURL).

Source code in src/gopher_mcp/models.py
@field_validator("host")
@classmethod
def validate_host(cls, v: str) -> str:
    """Validate hostname is not empty (mirrors GeminiURL)."""
    if not v.strip():
        raise ValueError("Host cannot be empty")
    return v.strip()

gopher_mcp.models.GeminiURL

Bases: BaseModel

Model for parsed Gemini URLs.

Based on the gemini://<host>[:<port>][/<path>][?<query>] format.

host class-attribute instance-attribute

host: str = Field(..., description='Hostname or IP address')

port class-attribute instance-attribute

port: int = Field(default=1965, description='Port number (default: 1965)')

path class-attribute instance-attribute

path: str = Field(default='/', description='Resource path')

query class-attribute instance-attribute

query: str | None = Field(None, description='Query string for user input')

validate_port classmethod

validate_port(v: int) -> int

Validate port number range.

Source code in src/gopher_mcp/models.py
@field_validator("port")
@classmethod
def validate_port(cls, v: int) -> int:
    """Validate port number range."""
    if not 1 <= v <= 65535:
        raise ValueError("Port must be between 1 and 65535")
    return v

validate_host classmethod

validate_host(v: str) -> str

Validate hostname is not empty.

Source code in src/gopher_mcp/models.py
@field_validator("host")
@classmethod
def validate_host(cls, v: str) -> str:
    """Validate hostname is not empty."""
    if not v.strip():
        raise ValueError("Host cannot be empty")
    return v.strip()

Caching and Security Models

gopher_mcp.models.CacheEntry

Bases: _BaseCacheEntry[GopherFetchResponse]

Model for Gopher cache entries.

key class-attribute instance-attribute

key: str = Field(..., description='Cache key')

value class-attribute instance-attribute

value: _CacheValueT = Field(..., description='Cached response')

timestamp class-attribute instance-attribute

timestamp: float = Field(..., description='Cache entry timestamp')

ttl class-attribute instance-attribute

ttl: int = Field(..., description='Time to live in seconds')

is_expired

is_expired(current_time: float) -> bool

Check if cache entry is expired.

Source code in src/gopher_mcp/models.py
def is_expired(self, current_time: float) -> bool:
    """Check if cache entry is expired."""
    return current_time - self.timestamp > self.ttl

gopher_mcp.models.GeminiCacheEntry

Bases: _BaseCacheEntry[GeminiFetchResponse]

Model for Gemini cache entries.

key class-attribute instance-attribute

key: str = Field(..., description='Cache key')

value class-attribute instance-attribute

value: _CacheValueT = Field(..., description='Cached response')

timestamp class-attribute instance-attribute

timestamp: float = Field(..., description='Cache entry timestamp')

ttl class-attribute instance-attribute

ttl: int = Field(..., description='Time to live in seconds')

is_expired

is_expired(current_time: float) -> bool

Check if cache entry is expired.

Source code in src/gopher_mcp/models.py
def is_expired(self, current_time: float) -> bool:
    """Check if cache entry is expired."""
    return current_time - self.timestamp > self.ttl

gopher_mcp.models.GeminiCertificateInfo

Bases: BaseModel

Model for client certificate information.

fingerprint class-attribute instance-attribute

fingerprint: str = Field(..., description='Certificate SHA-256 fingerprint')

subject class-attribute instance-attribute

subject: str = Field(..., description='Certificate subject')

issuer class-attribute instance-attribute

issuer: str = Field(..., description='Certificate issuer')

not_before class-attribute instance-attribute

not_before: str = Field(..., description='Certificate validity start')

not_after class-attribute instance-attribute

not_after: str = Field(..., description='Certificate validity end')

host class-attribute instance-attribute

host: str = Field(..., description='Associated hostname')

port class-attribute instance-attribute

port: int = Field(default=1965, description='Associated port')

path class-attribute instance-attribute

path: str = Field(default='/', description='Associated path scope')

gopher_mcp.models.TOFUEntry

Bases: BaseModel

Model for Trust-on-First-Use certificate storage.

host class-attribute instance-attribute

host: str = Field(..., description='Hostname')

port class-attribute instance-attribute

port: int = Field(default=1965, description='Port number')

fingerprint class-attribute instance-attribute

fingerprint: str = Field(..., description='Certificate SHA-256 fingerprint')

first_seen class-attribute instance-attribute

first_seen: float = Field(..., description='Timestamp of first connection')

last_seen class-attribute instance-attribute

last_seen: float = Field(..., description='Timestamp of last connection')

expires class-attribute instance-attribute

expires: float | None = Field(None, description='Certificate expiry timestamp')

is_expired

is_expired(current_time: float) -> bool

Check if certificate is expired.

Source code in src/gopher_mcp/models.py
def is_expired(self, current_time: float) -> bool:
    """Check if certificate is expired."""
    return self.expires is not None and current_time > self.expires