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 successful Gemini responses.

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 | bytes = Field(..., description='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 text `content` was truncated to the render limit (`size` still reports the full original size). Never set for binary.')

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